• 3D版翻页公告效果


    代码地址如下:
    http://www.demodashi.com/demo/12830.html

    前言:

    在逛小程序蘑菇街的时候,看到一个2D版滚动的翻页公告效果。其实看到这个效果的时候,一点都不觉得稀奇,因为之前也见过类似的。效果如下:

    蘑菇街效果.gif

    这里因为学习了3D平面的旋转,因此我自己也撸出了一个3D版的翻页公告效果:
    simple.gif

    是不是一下子觉得有趣多了呢,那就赶紧和我去看下如何做出这种效果吧 。

    使用:

    • 布局:
    <!--指定从下到上翻滚-->
    <com.xiangcheng.marquee3dview.Marquee3DView
        android:id="@+id/marquee3DView"
        android:layout_width="wrap_content"
        android:layout_height="25dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:direction="D2U"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        android:background="#FFC0CB"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/marquee3DView"
        app:layout_constraintStart_toStartOf="@+id/marquee3DView"
        app:layout_constraintTop_toBottomOf="@+id/marquee3DView">
        <!--从上到下翻滚-->
        <com.xiangcheng.marquee3dview.Marquee3DView
            android:id="@+id/marquee3DView2"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_marginStart="10dp"
            app:back_color="#00ffffff"
            app:direction="U2D"
            app:highlight_color="#FF6347"
            app:highlight_position="3"
            app:rotate_duration="1500"
            app:show_duration="1500" />
    </LinearLayout>
    
    • 属性:
    <declare-styleable name="Marquee3DView">
        <!--指定旋转的方向-->
        <attr name="direction" format="enum">
            <!--从上到下-->
            <enum name="U2D" value="2" />
            <!--从下到上-->
            <enum name="D2U" value="1" />
        </attr>
        <!--高亮的item位置-->
        <attr name="highlight_position" format="integer" />
        <!--item的颜色-->
        <attr name="back_color" format="color" />
        <!--高亮的文字、下划线颜色-->
        <attr name="highlight_color" format="color" />
        <!--3D旋转的时间-->
        <attr name="rotate_duration" format="integer" />
        <!--停留显示的时间-->
        <attr name="show_duration" format="integer" />
        <!--右边文字的颜色-->
        <attr name="label_text_color" format="color" />
        <!--右边文字的大小-->
        <attr name="label_text_size" format="dimension" />
        <!--指定左边图片的半径-->
        <attr name="label_bitmap_radius" format="dimension" />
        <!--bitmap和text之间的间距-->
        <attr name="label_bitmap_text_offset" format="dimension" />
    </declare-styleable>
    
    • 代码:
    /**
     * 设置显示的label
     * @param marqueeLabels
     */
    public void setMarqueeLabels(List<String> marqueeLabels)
    
    /**
     * 设置显示的bitmap
     * @param labelBitmap
     */
    public void setLabelBitmap(List<Bitmap> labelBitmap)
    
    /**
     * 点击监听
     * 
     */
    setOnWhereItemClick(new Marquee3DView.OnWhereItemClick() {
        @Override
        public void onItemClick(int position) {
            //TODO
        }
    });
    
    • gradle:
    compile 'com.xiangcheng:marquee3dlibs:1.0.1'
    
    • maven:
    <dependency>
      <groupId>com.xiangcheng</groupId>
      <artifactId>marquee3dlibs</artifactId>
      <version>1.0.1</version>
      <type>pom</type>
    </dependency>
    

    讲解:

    • 初始化属性
    private void initArgus(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Marquee3DView);
        backColor = typedArray.getColor(R.styleable.Marquee3DView_back_color, Color.parseColor("#cccccc"));
        direction = typedArray.getInt(R.styleable.Marquee3DView_direction, D2U);
        highLightPosition = typedArray.getInt(R.styleable.Marquee3DView_highlight_position, highLightPosition);
        highLightColor = typedArray.getColor(R.styleable.Marquee3DView_highlight_color, Color.parseColor("#FF1493"));
        rotateDuration = typedArray.getInt(R.styleable.Marquee3DView_rotate_duration, rotateDuration);
        showDuration = typedArray.getInt(R.styleable.Marquee3DView_show_duration, showDuration);
        labelColor = typedArray.getColor(R.styleable.Marquee3DView_label_text_color, Color.parseColor("#778899"));
        labelTextSize = (int) typedArray.getDimension(R.styleable.Marquee3DView_label_text_size, sp2px(15));
        labelBitmapRadius = (int) typedArray.getDimension(R.styleable.Marquee3DView_label_bitmap_radius, dp2px(10));
        labelBitmapTextOffset = (int) typedArray.getDimension(R.styleable.Marquee3DView_label_bitmap_text_offset, dp2px(10));
    }
    
    • 初始化变量
    private void initialize() {
        camera = new Camera();
        matrix = new Matrix();
    
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(labelTextSize);
        textPaint.setColor(labelColor);
    
        currentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        currentPaint.setTextSize(labelTextSize);
        currentPaint.setColor(labelColor);
    
        nextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        nextPaint.setTextSize(labelTextSize);
        nextPaint.setColor(labelColor);
    
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(highLightColor);
        linePaint.setStrokeWidth(dp2px(1));
        linePaint.setStyle(Paint.Style.FILL);
    
        highLightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        highLightPaint.setTextSize(sp2px(15));
        highLightPaint.setColor(highLightColor);
    
        textRegion = new Region();
    
        mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitmapPaint.setColor(Color.WHITE);
        mBitmapPaint.setStrokeWidth(0);
    }
    
    • 初始化动画
    private void initAnimation() {
        showItemRunable = new ShowItemRunable();
        //角度变化是0到90度的区间
        rotateAnimator = ValueAnimator.ofFloat(0, 90);
        rotateAnimator.setDuration(rotateDuration);
        rotateAnimator.setInterpolator(new LinearInterpolator());
        rotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                isRunning = true;
                //当前变化的角度变量,在绘制的时候使用
                changeRotate = (float) animation.getAnimatedValue();
                //计算当前的画笔的透明度(从255到0的过程)
                caculateCurrentPaint(changeRotate);
                //计算下一个item的画笔透明度(从0到255的过程)
                caculateNextPaint(changeRotate);
                //从0到height的一个过程,这里因为旋转的时候,同时还要进行平移
                translateY = height * animation.getAnimatedFraction();
                invalidate();
            }
        });
        //处理旋转结束后,停留一会显示
        rotateAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isRunning = false;
                postDelayed(showItemRunable, showDuration);
            }
        });
        //刚进来的时候,在第一个item上进行停留
        startRunable = new StartRunable();
        postDelayed(startRunable, showDuration);
    }
    
    //停留显示完的操作
    private class ShowItemRunable implements Runnable {
        @Override
        public void run() {
            currentItem++;
            if (currentItem >= marqueeLabels.size()) {
                currentItem = 0;
            }
            rotateAnimator.start();
        }
    }
    
    //刚进来时第一个item显示完后的操作
    private class StartRunable implements Runnable {
        @Override
        public void run() {
            hasStart = true;
            rotateAnimator.start();
        }
    }
    
    //当前画笔透明度的改变(255——>0)
    private void caculateCurrentPaint(float rotateAngle) {
        float percent = rotateAngle / 90;
        int alpha = (int) (255 - percent * 255);
        currentPaint.setAlpha(alpha);
    }
    
    //下一个item的画笔透明度的改变(0——>255)
    private void caculateNextPaint(float rotateAngle) {
        float percent = rotateAngle / 90;
        int alpha = (int) (percent * 255);
        nextPaint.setAlpha(alpha);
    }
    

    上面动画部分,其实你要关心的就是两个变量:changeRotate(0——>90度变化)translateY(0——>height变化)

    • 绘制
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (marqueeLabels == null || marqueeLabels.size() <= 0) {
            return;
        }
        drawCurrentItem(canvas);
        drawNextItem(canvas);
    }
    
    private void drawCurrentItem(Canvas canvas) {
        canvas.save();
        camera.save();
        if (direction == D2U) {
            //当前的item从下到上转动,逆时针旋转,角度是增大的过程
            camera.rotateX(changeRotate);
        } else {
            //从上到下旋转,顺时针旋转,角度是负角
            camera.rotateX(-changeRotate);
        }
        camera.getMatrix(matrix);
        camera.restore();
        if (direction == D2U) {
            将旋转中心至为下面一条边的中点上
            matrix.preTranslate(-width / 2, -height);
            //这里由于当前的item是往上转动的,下面的一条边最后是在0的位置了
            matrix.postTranslate(width / 2, height - translateY);
        } else {
            //这里如果是往下转动时,旋转中心就是上面一条边的中点了
            matrix.preTranslate(-width / 2, 0);
            //往下转动时,上面的边是不断地往下移动的,因此y轴是增大的
            matrix.postTranslate(width / 2, translateY);
        }
        //创建绘制的内容
        textBitmap = createChild(currentItem, false);
        canvas.drawBitmap(textBitmap, matrix, null);
        canvas.restore();
    }
    
    //这里用到了隔离绘制,将最后要画的东西都放到了bitmap上
    private Bitmap createChild(int position, boolean isNext) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawColor(backColor);
        if (labelBitmap != null && labelBitmap.size() > 0) {
            //绘制bitmap
            drawLabelBitmap(canvas, position);
        }
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        float allHeight = fontMetrics.descent - fontMetrics.ascent;
        float textWidth = textPaint.measureText(marqueeLabels.get(position));
        Rect rect = new Rect();
        rect.left = (int) labelTextStart;
        rect.right = (int) (labelTextStart + textWidth);
        rect.top = (int) (height / 2 - allHeight / 2);
        rect.bottom = (int) (height / 2 + allHeight / 2);
        textRegion.set(rect);
        //这里分是不是绘制下一个item
        if (isNext) {
            //如果是高亮的item,需要绘制下划线,以及改为高亮画笔
            if (highLightPosition == position) {
                caculateHighLightPaint(changeRotate, true);
                canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, highLightPaint);
                canvas.drawLine(labelTextStart, (float) (height * 1.0 / 2 + allHeight / 2),
                        labelTextStart + textWidth, (float) (height * 1.0 / 2 + allHeight / 2), linePaint);
            } else {
                canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, nextPaint);
            }
        } else {
            if (highLightPosition == position) {
                caculateHighLightPaint(changeRotate, false);
                canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, highLightPaint);
                canvas.drawLine(labelTextStart, (float) (height * 1.0 / 2 + allHeight / 2),
                        labelTextStart + textWidth, (float) (height * 1.0 / 2 + allHeight / 2), linePaint);
            } else {
                canvas.drawText(marqueeLabels.get(position), labelTextStart, height / 2 - allHeight / 2 - fontMetrics.ascent, currentPaint);
            }
        }
        return bitmap;
    }
    
    //绘制左边的bitmap
    private void drawLabelBitmap(Canvas canvas, int position) {
        int layer = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
        //先画圆,dst层
        canvas.drawCircle(labelBitmapRadius, height / 2, labelBitmapRadius, mBitmapPaint);
        //该mode下取两部分的交集部分
        mBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        //src层
        canvas.drawBitmap(labelBitmap.get(position), 0, height / 2 - labelBitmapRadius, mBitmapPaint);
        mBitmapPaint.setXfermode(null);
        canvas.restoreToCount(layer);
        labelTextStart = labelBitmapRadius * 2 + labelBitmapTextOffset;
    }
    
    //计算高亮的画笔的透明度,跟普通的画笔一样的算法
    private void caculateHighLightPaint(float rotate, boolean isNext) {
        if (isNext) {
            float percent = rotate / 90;
            int alpha = (int) (percent * 255);
            highLightPaint.setAlpha(alpha);
            linePaint.setAlpha(alpha);
        } else {
            float percent = rotate / 90;
            int alpha = (int) (255 - percent * 255);
            highLightPaint.setAlpha(alpha);
            linePaint.setAlpha(alpha);
        }
    }
    
    private void drawNextItem(Canvas canvas) {
        caculateNextItem();
        canvas.save();
        camera.save();
        if (direction == D2U) {
            //从下到上时,另外一个面初始位置是-90度,最后趋于0度位置
            camera.rotateX(-90 + changeRotate);
        } else {
            //从上到下是90度到0度的过程
            camera.rotateX(90 - changeRotate);
        }
        camera.getMatrix(matrix);
        camera.restore();
        if (direction == D2U) {
            //从下到上,旋转点是上面一条边的中点
            matrix.preTranslate(-width / 2, 0);
            //初始位置是height,最后到了0的位置
            matrix.postTranslate(width / 2, height + (-translateY));
        } else {
            //从上到下,旋转点是下面一条边的中点
            matrix.preTranslate(-width / 2, -height);
            //初始位置是0,最后到了height位置
            matrix.postTranslate(width / 2, translateY);
        }
        textBitmap = createChild(nextItem, true);
        canvas.drawBitmap(textBitmap, matrix, null);
        canvas.restore();
    }
    

    从上到下旋转示意图.png

    从下到上旋转示意图.png

    这里给出了两种情况旋转前旋转后的示意图,上面的平行四边形都是一个平面,可以想象下。

    其实讲解到这就基本没什么了,再就是一些细节性的代码了。如果有什么不明白的地方,可以互相交流。

    总结:

    (一):初始化一些需要的变量
    (二):初始化动画变量
    (三):绘制两个翻转的平面

    项目文件目录截图:

    项目目录结构


    3D版翻页公告效果

    代码地址如下:
    http://www.demodashi.com/demo/12830.html

    注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    Bug生产队 【Alpha】Scrum Meeting 4
    2020春软件工程助教工作总结 第十六周
    2020春软件工程助教工作总结 第十四周
    2020春软件工程助教工作总结 第十二周
    word2vec算法原理理解
    2020春软件工程助教工作总结 第十周
    2020春软件工程助教工作总结 第八周
    2020春软件工程助教工作总结 第六周
    2020春软件工程助教工作总结 第四周
    2020春软件工程助教工作总结 第三周
  • 原文地址:https://www.cnblogs.com/demodashi/p/9437133.html
Copyright © 2020-2023  润新知