高级UI-Canvas 使用,Canvas 实践 自定义Drawable

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liudao7994/article/details/79304709

canvas 基本方法

Canvas

直面意思是画布,其实是分装的一个工具类

一个Canvas类对象有四大基本要素

1、一个是用来保存像素的bitmap ----- 画板

2、一个Canvas在Bitmap上进行绘制操作 ---- 画布或者画纸(Layer—saveLayer操作时,新建一个透明的画布图层)

3、绘制的东西

4、绘制的画笔Paint

把我们的Canvas比喻成一块画板,为什么?

学习目标:

1、了解Canvas可以用来画些什么东西

除了常用的形状之外
画Region --- 区域的意思,它表示的Canvas图层上的一块封闭的区域

有以下几种方式:----具体含义 看图
DIFFERENCE(0),
    INTERSECT(1),
    UNION(2),
    XOR(3),
    REVERSE_DIFFERENCE(4),
    REPLACE(5);

mark

2、Canvas的变换技巧----了解Canvas里面的坐标系

Canvas里面牵扯两种坐标系:Canvas自己的坐标系、绘图坐标系

Canvas自己的坐标系 实际上就是画布的坐标系.

Canvas的坐标系,

	它就在View的左上角,做坐标原点往右是X轴正半轴,往下是Y轴的正半轴,有且只有一个,唯一不变

绘图坐标系

	它不是唯一不变的,它与Canvas的Matrix有关系,当Matrix发生改变的时候,绘图坐标系对应的进行改变,
	
	同时这个过程是不可逆的(save和restore方法来保存和还原变化操作)

	Matrix又是通过我们设置translate、rotate、scale、skew来进行改变的

3、Canvas的状态保存—状态栈、Layer栈

状态栈--save、 restore方法来保存和还原变换操作Matrix以及Clip剪裁

	也可以通过restoretoCount直接还原到对应栈的保存状态


Layer栈--- saveLayer的时候都会新建一个透明的图层(离屏Bitmap-离屏缓冲),并且会将saveLayer之前的一些Canvas操作延续过来
	  后续的绘图操作都在新建的layer上面进行
	  当我们调用restore 或者 restoreToCount 时 更新到对应的图层和画布上

save和restore方法来保存和还原变化操作

saveLayer的时候都会新建一个透明的图层

1、实现ReavlView效果 — 通过图片剪裁拼接(自定义Drawable实现)

2、自定义SearchView

Canvas 实践 ReavlView效果

http://fragmentapp.com/

实现以上效果

需要分阶段来

阶段一: 实现两张图片的拼接

从简单到困难, 先实现左半部分彩色右半部分灰色,采用自定义Drawable实现

涉及到的知识以及类: 矩阵Rect ,Drawable,Canvas裁剪等

理论分析一波:
先分析,现在有两张图片彩色和灰色,那么裁剪出的左边图片Drawable,右边图片Drawable,组合到一起.
代码:

package android.reavlviewdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    private ImageView mIv;
    private int[] mImgIds = new int[]{ //7个
            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 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
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        mIv = (ImageView) findViewById(R.id.iv);

        RevealDrawable revealDrawable = new RevealDrawable(
                getResources().getDrawable(R.drawable.avft),
                getResources().getDrawable(R.drawable.avft_active));

        mIv.setImageDrawable(revealDrawable);

    }
}

下面是自定义Drawable

package android.reavlviewdemo;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Gravity;

/**
 * @author liuml
 * @explain
 * @time 2018/2/3 15:44
 */

public class RevealDrawable extends Drawable {

    //Drawable 需要实现setBounds

    //灰色部分
    private Drawable mUnselectDrawable;
    //彩色部分
    private Drawable mSelectDrawable;

    private Rect mTempRect = new Rect();

    public RevealDrawable(Drawable mUnselectDrawable, Drawable mSelectDrawable) {
        this.mSelectDrawable = mSelectDrawable;
        this.mUnselectDrawable = mUnselectDrawable;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {


        //将两张图片进行裁剪和拼接

        //1 ===== 画左边的图片
        //获取边界矩形
        Rect bounds = getBounds();
        //初始化Rect
        Rect r = mTempRect;
        //获取边界矩阵的宽高
        int w = bounds.width();
        int h = bounds.height();

        //从一个矩形区域裁剪出目标矩形
        Gravity.apply(
                Gravity.LEFT,// 从哪个方向开始剪,左边还是右边
                w / 2,// 目标矩形的宽
                h, // 目标矩形的高
                bounds,// 被剪裁图片的rect
                r// 目标rect
        );
        canvas.save();// 因为下面需要对canvas进行裁剪 先保存一次canvas,后面进行还原.
        canvas.clipRect(r);//裁剪目标rect
        //画出
        mUnselectDrawable.draw(canvas);

        canvas.restore();//还原canvas 方便下一次进行裁剪.
        //2 =====画右边的图片

        //同理右边
        Gravity.apply(
                Gravity.RIGHT,
                w / 2,
                h,
                bounds,
                r
        );
        canvas.save();
        canvas.clipRect(r);
        mSelectDrawable.draw(canvas);
        canvas.restore();

    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        //定义两张图片的宽高
        mSelectDrawable.setBounds(bounds);
        mUnselectDrawable.setBounds(bounds);
    }

    @Override
    public int getIntrinsicHeight() {//返回可绘制的固有高度
        return Math.max(mSelectDrawable.getIntrinsicHeight(), mUnselectDrawable.getIntrinsicHeight());
    }

    @Override
    public int getIntrinsicWidth() {//返回可绘制的固有宽度
        return Math.max(mSelectDrawable.getIntrinsicWidth(), mUnselectDrawable.getIntrinsicWidth());
    }

    @Override
    public void setAlpha(int alpha) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}

图片资源

mark

效果图

mark


以上完成了基本的裁剪. 下面需要完成可以动态的改变裁剪的距离.

阶段二 : 动态改变图片的裁剪距离

上面裁剪的时候只是从每张图片的中间裁剪,下面需要动态改变图片的裁剪大小.

实现动态改变有两个重要的方法 getLevel 和setImageLevel 看下Drawable 的源码

    /**
     * Retrieve the current level.
        检索当前的水平  实际上就是检索当前的级别 可以看到参数
        int当前级别,从0(最小值)到10000(最大值)。
     *
     * @return int Current level, from 0 (minimum) to 10000 (maximum).
     */
    public final @IntRange(from=0,to=10000) int getLevel() {
        return mLevel;
    }
    
    
    /**
     * Sets the image level, when it is constructed from a
     * {@link android.graphics.drawable.LevelListDrawable}.
     设置图像级别,当它是由a构建的
     *
     * @param level The new level for the image.
     */
    @android.view.RemotableViewMethod
    public void setImageLevel(int level) {
        mLevel = level;
        if (mDrawable != null) {
            mDrawable.setLevel(level);
            resizeFromDrawable();
        }
    }

分别是 获取当前Level 和设置图片的Level

有了这个就可以根据Level 的大小来改变裁剪的位置以及大小.

mark

1.1 最复杂的是两张图片进行拼接,一边灰色,一边彩色的

Drawable 状态,可以根据level来切换不同的状态绘制,level值是从0 ~ 10000进行变化
1)全灰色  --- 0 或者 10000

2)全彩色  --- 5000

3)左边灰色,右边彩色  5000 ~ 0

4)左边彩色,右边灰色 10000 ~ 5000

下面做下根据点击图片动态改变Level 来改变裁剪的大小. 上代码

package android.reavlviewdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {
    private  int level = 10000;
    private ImageView mIv;
    private int[] mImgIds = new int[]{ //7个
            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 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
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        mIv = (ImageView) findViewById(R.id.iv);

        RevealDrawable revealDrawable = new RevealDrawable(
                getResources().getDrawable(R.drawable.avft),
                getResources().getDrawable(R.drawable.avft_active));

        mIv.setImageDrawable(revealDrawable);

        mIv.setImageLevel(5000);
        mIv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //if(level > 0){
                level -= 500;
                //}
                mIv.setImageLevel(level);
            }
        });

    }
}

下面是自定义view


package android.reavlviewdemo;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Gravity;

/**
 * @author liuml
 * @explain
 * @time 2018/2/3 15:44
 */

public class RevealDrawable extends Drawable {

    //Drawable 需要实现setBounds

    //灰色部分
    private Drawable mUnselectDrawable;
    //彩色部分
    private Drawable mSelectDrawable;

    private Rect mTempRect = new Rect();

    public RevealDrawable(Drawable mUnselectDrawable, Drawable mSelectDrawable) {
        this.mSelectDrawable = mSelectDrawable;
        this.mUnselectDrawable = mUnselectDrawable;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {


        //将两张图片进行裁剪和拼接
        int level = getLevel();
        if (level == 0 || level == 10000) {
            // 画整张灰色的图
            mUnselectDrawable.draw(canvas);
        } else if (level == 5000) {
            // 画整张彩色的图
            mSelectDrawable.draw(canvas);
        } else {

            //获取边界矩形
            Rect bounds = getBounds();
            //初始化Rect
            Rect r = mTempRect;

            // 比例 -1 ~ 1 之间进行变化 -1 ~0 表示左边灰色,右边彩色
            // 0 ~1 表示左边是彩色,右边灰色
            float ratio = (level / 5000f) - 1f;


            //1. 画出灰色区域
            {
                //获取边界矩阵的宽高
                int w = bounds.width();
                int h = bounds.height();
                //判断从哪个方向进行裁剪 可以看下图 当0-5000时左边是灰色右边是彩色, 当5000-10000时 左边是彩色右边是灰色
                int gravity = ratio < 0 ? Gravity.LEFT : Gravity.RIGHT;

                //目标裁剪的宽度 动态计算
                w = (int) (w * Math.abs(ratio));
                //从一个矩形区域裁剪出目标矩形
                Gravity.apply(
                        gravity,// 从哪个方向开始剪,左边还是右边
                        w,// 目标矩形的宽
                        h, // 目标矩形的高
                        bounds,// 被剪裁图片的rect
                        r// 目标rect
                );
                canvas.save();// 因为下面需要对canvas进行裁剪 先保存一次canvas,后面进行还原.
                canvas.clipRect(r);//裁剪目标rect
                //画出
                mUnselectDrawable.draw(canvas);

                canvas.restore();//还原canvas 方便下一次进行裁剪.
            }

            //2. 画出才彩色区域
            {
                //获取边界矩阵的宽高
                int w = bounds.width();
                int h = bounds.height();
                int gravity = ratio < 0 ? Gravity.RIGHT : Gravity.LEFT;
                //目标裁剪的宽度 动态计算  这里是剩下的距离
                w -= (int) (w * Math.abs(ratio));
                Gravity.apply(
                        gravity,
                        w,
                        h,
                        bounds,
                        r
                );
                canvas.save();
                canvas.clipRect(r);
                mSelectDrawable.draw(canvas);
                canvas.restore();
            }
        }


    }


    @Override
    protected boolean onLevelChange(int level) {
        // 当设置level时,来重绘Drawable
        invalidateSelf();
        return super.onLevelChange(level);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        //定义两张图片的宽高
        mSelectDrawable.setBounds(bounds);
        mUnselectDrawable.setBounds(bounds);
    }

    @Override
    public int getIntrinsicHeight() {//返回可绘制的固有高度
        return Math.max(mSelectDrawable.getIntrinsicHeight(), mUnselectDrawable.getIntrinsicHeight());
    }

    @Override
    public int getIntrinsicWidth() {//返回可绘制的固有宽度
        return Math.max(mSelectDrawable.getIntrinsicWidth(), mUnselectDrawable.getIntrinsicWidth());
    }

    @Override
    public void setAlpha(int alpha) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}


笔记:

RevealView 效果—(来源 http://fragmentapp.com/)

1、自定义的Drawable来做

1.1 最复杂的是两张图片进行拼接,一边灰色,一边彩色的

Drawable 状态,可以根据level来切换不同的状态绘制,level值是从0 ~ 10000进行变化
1)全灰色  --- 0 或者 10000

2)全彩色  --- 5000

3)左边灰色,右边彩色  5000 ~ 0

4)左边彩色,右边灰色 10000 ~ 5000

2、水平ScrollView来实现滑动

ScrollView里面一层布局,布局里面加载一组ImageView

SearchView效果

状态分解:
1、默认状态:圆圈、手柄

2、展开动画状态:1)动画前半段,圆圈减小至消失,底部横线加长

		 2)动画前半段,圆圈消失后,手柄减小,底部横线加长

3、可以动手实现下还原动画

Canvas 双缓冲

猜你喜欢

转载自blog.csdn.net/liudao7994/article/details/79304709