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);
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效果
实现以上效果
需要分阶段来
阶段一: 实现两张图片的拼接
从简单到困难, 先实现左半部分彩色右半部分灰色,采用自定义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;
}
}
图片资源
效果图
以上完成了基本的裁剪. 下面需要完成可以动态的改变裁剪的距离.
阶段二 : 动态改变图片的裁剪距离
上面裁剪的时候只是从每张图片的中间裁剪,下面需要动态改变图片的裁剪大小.
实现动态改变有两个重要的方法 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 的大小来改变裁剪的位置以及大小.
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 双缓冲