您的位置:首页 > 产品设计 > UI/UE

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

2018-02-10 14:39 344 查看
canvas 基本方法

Canvas 实践 ReavlView效果

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

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

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效果

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;
}
}


图片资源



效果图



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

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

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

实现动态改变有两个重要的方法 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 双缓冲
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  自定义view Canvas