zoukankan      html  css  js  c++  java
  • 高级UI-画板Canvas

    Canvas可以用来绘制直线、点、几何图形、曲线、Bitmap、圆弧等等,做出很多很棒的效果,例如QQ的消息气泡就是使用Canvas画的

    Canvas中常用的方法

    • 初始化参数
    Paint paint = new Paint();
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);
    paint.setStrokeWidth(8);
    
    • 绘制直线
    canvas.drawLine(0, 0, 100, 100, paint);
    
    • 绘制一组直线
    float[] lines = {0, 0, 100, 100, 200, 200, 300, 300};
    canvas.drawLines(lines,paint);
    
    • 绘制点
    canvas.drawPoint(100, 100, paint);
    
    • 绘制矩形
    Rect rect = new Rect(0, 0, 200, 100);
    canvas.drawRect(rect, paint);
    //canvas.drawRect(0, 0, 200, 100, paint);
    
    • 绘制圆角矩形
    RectF rectF = new RectF(100, 100, 300, 200);
    canvas.drawRoundRect(rectF, 20, 20, paint);
    
    • 绘制圆形
    canvas.drawCircle(300, 300, 200, paint);
    canvas.drawOval(100, 100, 300, 200, paint);
    
    • 绘制弧度
    RectF rectF = new RectF(100, 100, 300, 200);
    canvas.drawArc(rectF, 0, 90, true, paint);
    

    使用Path参与绘制

    • 绘制直线
    //使用Path
    Path path = new Path();
    //落笔位置
    path.moveTo(100, 100);
    //移动
    path.lineTo(200, 100);
    path.lineTo(200, 200);
    //闭合线
    path.close();
    //按路径绘制
    canvas.drawPath(path, paint);
    
    • 绘制其他线条,使用path.addXxx()
    float[] radii = {10, 10, 20, 30, 40, 40, 60, 50};
    RectF rectF = new RectF(100, 100, 600, 500);
    path.addRoundRect(rectF, radii, Path.Direction.CCW);
    canvas.drawPath(path, paint);
    

    使用Region区域绘制

    //创建一块矩形区域
    Region region = new Region(100, 100, 500, 400);
    RegionIterator iterator = new RegionIterator(region);
    Rect rect = new Rect();
    while (iterator.next(rect)) {
        canvas.drawRect(rect, paint);
    }
    

    以上只是画出一个矩形,另外并没有什么现象,这是因为只有一个Region
    两个Region实现取交集,使用并且不断分割交集部分

    Path path = new Path();
    RectF rectF = new RectF(100, 100, 600, 800);
    path.addOval(rectF, Path.Direction.CCW);
    Region region1 = new Region();
    Region region2 = new Region(100, 100, 500, 400);
    region1.setPath(path, region2);
    RegionIterator iterator = new RegionIterator(region1);
    Rect rect = new Rect();
    while (iterator.next(rect)) {
    	canvas.drawRect(rect, paint);
    }
    

    以上执行结果如下
    画板Canvas-Region效果
    Region的其他操作
    并集:region.union(r);
    交集:region.op(r, Op.INTERSECT);

    Canvas的细节问题

    当canvas执行drawXXX的时候就会新建一个新的画布图层
    虽然新建一个画布图层,但是还是会沿用之前设置的平移变换,不可逆的(save和restore来解决)
    之所以这样设计,是考虑到了绘制复杂图形的时候,可能会变换画布位置,那么就会造成之前绘制的图像发生错位,导致前功尽弃

    Canvas变换

    平移(Translate)

    Rect rect = new Rect(100, 100, 800, 1000);
    paint.setColor(Color.RED);
    canvas.drawRect(rect, paint);
    canvas.translate(100, 100);
    paint.setColor(Color.GREEN);
    canvas.drawRect(rect, paint);
    

    画板Canvas-平移

    缩放(Scale)

    Rect rect = new Rect(100, 100, 600, 800);
    paint.setColor(Color.RED);
    canvas.drawRect(rect, paint);
    canvas.scale(1.2F, 1.5F);
    paint.setColor(Color.GREEN);
    canvas.drawRect(rect, paint);
    

    画板Canvas-缩放

    旋转(Rotate)

    Rect rect = new Rect(400, 100, 700, 800);
    paint.setColor(Color.RED);
    canvas.drawRect(rect, paint);
    canvas.rotate(25);//默认围绕原点
    //canvas.rotate(25, 400, 100);//指定旋转点
    paint.setColor(Color.GREEN);
    canvas.drawRect(rect, paint);
    

    画板Canvas-旋转

    斜拉画布(Skew)

    Rect rect = new Rect(100, 100, 400, 800);
    paint.setColor(Color.RED);
    canvas.drawRect(rect, paint);
    canvas.skew(0.5F, 0);
    paint.setColor(Color.GREEN);
    canvas.drawRect(rect, paint);
    

    画板Canvas-斜拉

    裁剪画布(clip)

    RectF rectF = new RectF(100, 100, 500, 800);
    paint.setColor(Color.RED);
    canvas.drawRect(rectF, paint);
    paint.setColor(Color.GREEN);
    canvas.clipRect(new Rect(200, 200, 400, 400));
    canvas.drawColor(Color.BLUE);
    

    画板Canvas-裁剪

    变换操作的影响

    当对画板进行操作以后,会对后续的画布操作造成影响,那么要实现不对后续操作就需要在操作前保存画布,需要时候恢复画布

    canvas.save(); //保存当前画布
    ···//画布操作
    canvas.restore();//恢复保存的画布
    

    canvas实际上是保存到画布栈里面去了,每一次保存,就使得一个当前画布入栈,每一次恢复,就有一个canvas出栈
    因此,一般来说保存和恢复是成对出现的

    自定义Drawable动画

    Drawable就是一个可画的对象,其可能是一张位图(BitmapDrawable),也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的可画对象,就可以将这个可画对象当作一块“画布(Canvas)”,在其上面操作可画对象,并最终将这种可画对象显示在画布上,有点类似于"内存画布"
    自定义Drawable

    public class RevealDrawableView extends Drawable {
    
        private int orientation;
        private Drawable selected;
        private Drawable unselected;
        private int widthLeft;
        private int heightLeft;
        private int widthRight;
        private int heightRight;
        public static final int HORIZONTAL = 1;
        public static final int VERTICAL = 2;
    
        public RevealDrawableView(Drawable unselected, Drawable selected, int orientation) {
            this.unselected = unselected;
            this.selected = selected;
            this.orientation = orientation;
        }
    
        @Override
        public void draw(@NonNull Canvas canvas) {
    
            //得到当前level,转化成百分比
            int level = getLevel();
            if (level == 10000 || level == 0) { //滑出画入状态
                unselected.draw(canvas);
            } else if (level == 5000) { //在正中间状态
                selected.draw(canvas);
            } else {
                Rect bounds = getBounds();//得到当前Drawable自身的矩形区域
                float ratio = (level / 5000F) - 1F;//得到比例-1~+1
                int width = bounds.width();
                int height = bounds.height();
                widthLeft = widthRight = width;
                heightLeft = heightRight = height;
                //得到左右宽高
                if (orientation == HORIZONTAL) {
                    widthLeft = (int) (width * Math.abs(ratio));
                    widthRight = width - widthLeft;
                }
                if (orientation == VERTICAL) {
                    heightLeft = (int) (height * Math.abs(ratio));
                    heightRight = height - heightLeft;
                }
                int gravity = ratio < 0 ? Gravity.LEFT : Gravity.RIGHT;
                //得到当前左边区域
                Rect rectLeft = new Rect();
                //抠图位置,宽度,高度,目标,输出位置
                Gravity.apply(gravity, widthLeft, heightLeft, bounds, rectLeft);
                canvas.save();//保存画布
                canvas.clipRect(rectLeft);//剪切画布
                unselected.draw(canvas);//画未选中图片
                canvas.restore();//恢复画布
    
                //得到右边矩形区域
                gravity = ratio < 0 ? Gravity.RIGHT : Gravity.LEFT;
                Rect rectRight = new Rect();
                //抠图位置,宽度,高度,目标,输出位置
                Gravity.apply(gravity, widthRight, heightRight, bounds, rectRight);
                canvas.save();
                canvas.clipRect(rectRight);
                selected.draw(canvas);//画选中图片
                canvas.restore();
            }
        }
    
        @Override
        protected void onBoundsChange(Rect bounds) {
            //设置矩形
            unselected.setBounds(bounds);
            selected.setBounds(bounds);
        }
    
        @Override
        public int getIntrinsicWidth() {
            //得到Drawable的实际宽度
            return Math.max(unselected.getIntrinsicWidth(), selected.getIntrinsicWidth());
        }
    
        @Override
        public int getIntrinsicHeight() {
            //得到Drawable的实际高度
            return Math.max(unselected.getIntrinsicHeight(), selected.getIntrinsicHeight());
        }
    
        @Override
        protected boolean onLevelChange(int level) {
            //每次level改变时刷新
            invalidateSelf();
            return true;
        }
    
        @Override
        public void setAlpha(int alpha) {
    
        }
    
        @Override
        public void setColorFilter(@Nullable ColorFilter colorFilter) {
    
        }
    
        @Override
        public int getOpacity() {
            return PixelFormat.UNKNOWN;
        }
    }
    

    自定义控件

    public class GallaryHScrollView extends HorizontalScrollView implements OnTouchListener {
    
        private LinearLayout container;
        private int centerX;
        private int width;
    
        public GallaryHScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public GallaryHScrollView(Context context) {
            super(context);
            init();
        }
    
        private void init() {
            //ScrollView水平滑动,存在大量ImageView
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.WRAP_CONTENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT);
            container = new LinearLayout(getContext());
            container.setLayoutParams(params);
            setOnTouchListener(this);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            //得到某一张图片的宽度
            View view = container.getChildAt(0);
            width = view.getWidth();
            //得到中间x坐标
            centerX = getWidth() / 2;
            //中心坐标改为中心图片的左边界
            centerX = centerX - width / 2;
            //给LinearLayout和hzv之间设置边框距离
            container.setPadding(centerX, 0, centerX, 0);
        }
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                //渐变图片
                reveal();
            }
            return false;
        }
    
        private void reveal() {
            // 渐变效果
            //得到滑出去的距离
            int scrollX = getScrollX();
            //找到两张渐变的图片的下标--左,右
            int index_left = scrollX / width;
            int index_right = index_left + 1;
            //设置图片的level
            for (int i = 0; i < container.getChildCount(); i++) {
                if (i == index_left || i == index_right) {
                    //变化
                    float ratio = 5000f / width;//比例
                    ImageView iv_left = (ImageView) container.getChildAt(index_left);
                    //scrollX%icon_代表滑出去的距离
                    iv_left.setImageLevel((int) (5000 - scrollX % width * ratio));
                    //右边
                    if (index_right < container.getChildCount()) {
                        ImageView iv_right = (ImageView) container.getChildAt(index_right);
                        //scrollX%icon_代表滑出去的距离
                        //滑出去了icon_width/2  icon_width/2%icon_width
                        iv_right.setImageLevel((int) (10000 - scrollX % width * ratio));
                    }
                } else {
                    //灰色
                    ImageView iv = (ImageView) container.getChildAt(i);
                    iv.setImageLevel(0);
                }
            }
        }
    
        //添加图片的方法
        public void addImageViews(Drawable[] revealDrawables) {
            for (int i = 0; i < revealDrawables.length; i++) {
                ImageView img = new ImageView(getContext());
                img.setImageDrawable(revealDrawables[i]);
                container.addView(img);
                if (i == 0) {
                    img.setImageLevel(5000);
                }
            }
            addView(container);
        }
    }
    

    测试工具类

    public class SourceUtils {
        private static int[] mImgIds = new int[]{
                R.drawable.avft, R.drawable.box_stack, R.drawable.bubble_frame,
                R.drawable.bubbles, R.drawable.bullseye, R.drawable.circle_filled,
                R.drawable.circle_outline, R.drawable.avft, R.drawable.box_stack,
                R.drawable.bubble_frame, R.drawable.bubbles, R.drawable.bullseye,
                R.drawable.circle_filled, R.drawable.circle_outline
        };
        private static int[] mImgIds_active = new int[]{
                R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
                R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
                R.drawable.circle_outline_active, R.drawable.avft_active, R.drawable.box_stack_active,
                R.drawable.bubble_frame_active, R.drawable.bubbles_active, R.drawable.bullseye_active,
                R.drawable.circle_filled_active, R.drawable.circle_outline_active
        };
    
        public static int[] getImgIds() {
            return mImgIds;
        }
    
        public static int[] getImgIdsActive() {
            return mImgIds_active;
        }
    }
    

    布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.cj5785.testcanvas.GallaryHScrollView
            android:id="@+id/ghs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:background="@android:color/darker_gray"
            android:scrollbars="none" />
    
    </LinearLayout>
    

    测试

    public class RevealDrawableActivity extends AppCompatActivity {
    
        private Drawable[] revealDrawables;
        private GallaryHScrollView gallaryHScrollView;
        protected int level = 10000;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_reveal_drawable);
            revealDrawables = new Drawable[SourceUtils.getImgIds().length];
            for (int i = 0; i < SourceUtils.getImgIds().length; i++) {
                RevealDrawableView rd = new RevealDrawableView(
                        getResources().getDrawable(SourceUtils.getImgIds()[i]),
                        getResources().getDrawable(SourceUtils.getImgIdsActive()[i]),
                        RevealDrawableView.HORIZONTAL);
                revealDrawables[i] = rd;
            }
            gallaryHScrollView = (GallaryHScrollView) findViewById(R.id.ghs);
            gallaryHScrollView.addImageViews(revealDrawables);
        }
    }
    

    效果
    画板Canvas-自定义Drawable

    自定义控件Search动画

    自定义View控件

    public class SearchView extends View {
        private Paint paint;
        private BaseController controller;
    
        public SearchView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            paint = new Paint();
            paint.setStrokeWidth(8);
        }
    
        public void setController(BaseController controller) {
            this.controller = controller;
            controller.setSearchView(this);
            invalidate();
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            controller.draw(canvas, paint);
        }
    
        public void startAnimation() {
            if (controller != null) {
                controller.startAnim();
            }
        }
    
        public void resetAnimation() {
            if (controller != null) {
                controller.resetAnim();
            }
        }
    }
    

    通过控制器来控制绘画

    public abstract class BaseController {
    
        public static final int STATE_ANIM_RESET = 0;
        public static final int STATE_ANIM_START = 1;
        public int state = STATE_ANIM_RESET;
    
        public float progress = -1;
        private SearchView searchView;
    
        public abstract void draw(Canvas canvas, Paint paint);
    
        public void startAnim() {
    
        }
    
        public void resetAnim() {
    
        }
    
        public int getWidth() {
            return searchView.getWidth();
        }
    
        public int getHeight() {
            return searchView.getHeight();
        }
    
        public ValueAnimator startValueAnimation() {
            final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
            animator.setDuration(5000);
            animator.setInterpolator(new AnticipateInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    progress = (float) animator.getAnimatedValue();
                    searchView.invalidate();
                }
            });
            animator.start();
            progress = 0;
            return animator;
        }
    
        public void setSearchView(SearchView searchView) {
            this.searchView = searchView;
        }
    }
    

    实现控制器方法

    public class MyController extends BaseController {
    
        private int color = Color.GREEN;
        private int cx, cy, cr;
        private RectF rectF;
        private int j = 15;
    
        public MyController() {
            rectF = new RectF();
        }
    
        @Override
        public void draw(Canvas canvas, Paint paint) {
            canvas.drawColor(color);
            switch (state) {
                case STATE_ANIM_START:
                    drawStartAnimView(canvas, paint);
                    break;
                case STATE_ANIM_RESET:
                    drawResetAnimView(canvas, paint);
                    break;
            }
        }
    
        private void drawStartAnimView(Canvas canvas, Paint paint) {
            canvas.save();
            if (progress <= 0.5f) {
                //绘制圆和把手
                canvas.drawArc(rectF, 45, 360 * (progress * 2 - 1),
                        false, paint);
                canvas.drawLine(rectF.right - j, rectF.bottom - j,
                        rectF.right + cr - j, rectF.bottom + cr - j, paint);
            } else {
                //绘制把手
                canvas.drawLine(
                        rectF.right - j + cr * (progress * 2 - 1),
                        rectF.bottom - j + cr * (progress * 2 - 1),
                        rectF.right - j + cr, rectF.bottom + cr - j, paint);
            }
            //绘制下面的横线
            canvas.drawLine(
                    (rectF.right - j + cr) * (1 - progress * 0.8f), rectF.bottom + cr - j,
                    rectF.right - j + cr, rectF.bottom + cr - j, paint);
            canvas.restore();
            rectF.left = cx - cr + progress * 250;
            rectF.right = cx + cr + progress * 250;
            rectF.top = cy - cr;
            rectF.bottom = cy + cr;
        }
    
        private void drawResetAnimView(Canvas canvas, Paint paint) {
            cr = getWidth() / 20;
            cx = getWidth() / 2;
            cy = getHeight() / 2;
            rectF.left = cx - cr;
            rectF.right = cx + cr;
            rectF.top = cy - cr;
            rectF.bottom = cy + cr;
            canvas.save();
            paint.reset();
            paint.setAntiAlias(true);
            paint.setColor(Color.WHITE);
            paint.setStrokeWidth(5);
            paint.setStyle(Paint.Style.STROKE);
            canvas.rotate(45, cx, cy);
            canvas.drawLine(cx + cr, cy, cx + cr * 2, cy, paint);
            canvas.drawArc(rectF, 0, 360, false, paint);
            canvas.restore();
        }
    
        @Override
        public void startAnim() {
            super.startAnim();
            state = STATE_ANIM_START;
            startValueAnimation();
        }
    
        @Override
        public void resetAnim() {
            super.resetAnim();
            state = STATE_ANIM_RESET;
            startValueAnimation();
        }
    }
    

    布局

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.cj5785.testcanvas.search.SearchView
            android:id="@+id/search_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:text="start"
            android:onClick="start"/>
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:text="reset"
            android:onClick="reset"/>
    
    </RelativeLayout>
    

    测试

    public class SearchActivity extends AppCompatActivity {
    
        private SearchView serachView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_search);
            serachView = findViewById(R.id.search_view);
            serachView.setController(new MyController());
        }
    
        public void start(View view) {
            serachView.startAnimation();
        }
    
        public void reset(View view) {
            serachView.resetAnimation();
        }
    }
    

    测试结果
    画板Canvas-自定义search

  • 相关阅读:
    _1_html_框架
    _0_web_基础
    _0_工具
    虚拟机安装与使用
    NumPy数据类型
    NumPy Ndarray对象
    机器学习之K-近邻(KNN)算法
    vue项目如何打包扔向服务器
    Eslint 规则说明
    Python ssh 远程执行shell命令
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664581.html
Copyright © 2011-2022 走看看