博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
高级UI-画板Canvas
阅读量:4878 次
发布时间:2019-06-11

本文共 17081 字,大约阅读时间需要 56 分钟。

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参与绘制

  • 绘制直线
//使用PathPath 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_width:代表滑出去的距离 iv_left.setImageLevel((int) (5000 - scrollX % width * ratio)); //右边 if (index_right < container.getChildCount()) {
ImageView iv_right = (ImageView) container.getChildAt(index_right); //scrollX%icon_width:代表滑出去的距离 //滑出去了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; }}

布局

测试

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

布局

测试

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

转载于:https://www.cnblogs.com/cj5785/p/10664581.html

你可能感兴趣的文章
Windows 程序 HelloWorld
查看>>
1.1 Python是一种什么样的语言
查看>>
Spring Boot (十):邮件服务
查看>>
angular4 使用window事件【转】
查看>>
cJSON使用
查看>>
读书笔记:《C#与.NET4高级程序设计》-核心部分
查看>>
HDU 2100 LoveKey
查看>>
PAT L2-019 悄悄关注
查看>>
HDU 2039 三角形
查看>>
Softmax实现 fashion.mnist 分类
查看>>
统计学习-朴素贝叶斯法
查看>>
学习进度17
查看>>
编译原理——算符优先分析文法(附源代码)
查看>>
jboss的启动过程
查看>>
渲染部分
查看>>
力扣——所有可能的路径
查看>>
关于VS项目平台的x86,x64,Any CPU以及Debug和Release的区别
查看>>
解密module_init幕后的故事
查看>>
9个移动网站优化的最佳实践
查看>>
李昌镐:苍老的青春(转载) 韩国围棋职业棋手
查看>>