zoukankan      html  css  js  c++  java
  • 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大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    219. Contains Duplicate II
    189. Rotate Array
    169. Majority Element
    122. Best Time to Buy and Sell Stock II
    121. Best Time to Buy and Sell Stock
    119. Pascal's Triangle II
    118. Pascal's Triangle
    88. Merge Sorted Array
    53. Maximum Subarray
    CodeForces 359D Pair of Numbers (暴力)
  • 原文地址:https://www.cnblogs.com/demodashi/p/9437133.html
Copyright © 2011-2022 走看看