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

以上执行结果如下



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

缩放(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);

旋转(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);

斜拉画布(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);

裁剪画布(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.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;
}
}

布局

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

效果

自定义控件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();
}
}

测试结果

高级UI-画板Canvas的更多相关文章

  1. Android 高级UI设计笔记07:RecyclerView 的详解

    1. 使用RecyclerView       在 Android 应用程序中列表是一个非常重要的控件,适用场合非常多,如新闻列表.应用列表.消息列表等等,但是从Android 一出生到现在并没有非常 ...

  2. firefox 扩展开发笔记(三):高级ui交互编程

    firefox 扩展开发笔记(三):高级ui交互编程 前言 前两篇链接 1:firefox 扩展开发笔记(一):jpm 使用实践以及调试 2:firefox 扩展开发笔记(二):进阶开发之移动设备模拟 ...

  3. 高级UI晋升之View渲染机制(二)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 优化性能一般从渲染,运算与内存,电量三个方面进行,今天开始说聊一聊Android ...

  4. iOS开发——高级UI&带你玩转UITableView

    带你玩装UITableView 在实际iOS开发中UITableView是使用最多,也是最重要的一个控件,如果你不会用它,那别说什么大神了,菜鸟都不如. 其实关于UItableView事非常简单的,实 ...

  5. 创新高性能移动 UI 框架-Canvas UI 框架

    WebView 里无法获得的能力虽然是「体验增强」与「端基本能力」,但现都基本上有成熟解决方法.但后期的 UI 和 Layout 的性能反而是目前 Web 技术欠缺的.所以,无论是 Titanium ...

  6. Unity5.3——UI之Canvas

    原文:http://docs.unity3d.com/Manual/UISystem.html Canvas 所有的UI都应该放在Canvas里面(子层).Canvas是一个带有Canvas组件的Ga ...

  7. canvas画画板,canvas画五角星,canvas制作钟表、Konva写钟表

    制作一个画画板,有清屏有橡皮擦有画笔可以换颜色 style样式 <head> <meta charset="UTF-8"> <title>画画板 ...

  8. 高级UI晋升之自定义View实战(九)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 1.前言: 本文采用自定义view的方法来实现一键清除的动画这个功能. 2.效果 ...

  9. 高级UI晋升之自定义View实战(六)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从Android 自定义属性动画&Camera动画来介绍自定义V ...

随机推荐

  1. background-image:url为空引发的两次请求问题

    参考文章: https://blog.csdn.net/jsjhushilei/article/details/51101014 1.Nicholas 在 2009 年就开始推动各浏览器厂商,现在看起 ...

  2. Hive中的SQL执行计划--几乎所有的SQL都有

    explain SQL 会解释SQL的执行过程

  3. [Codeforces 1251F]Red-White Fence

    Description 题库链接 给你 \(n\) 块白木板,\(k\) 块红木板,分别有各自的长度 \(h_i\).让你用这些木板组成一段围栏,要满足: 只用一块红木板,且所有白木板的长度均严格小于 ...

  4. C++ socket bind() 函数绑定错误

    VS2015编译错误: errorCxxxx: 'initializing' : cannot convert from 'std::_Bind<false,void,SOCKET&,s ...

  5. WinDbg常用命令系列---.cordll (控制CLR调试)

    .cordll (控制CLR调试) 简介 .cordell命令控制托管代码调试和Microsoft.NET公共语言运行库(CLR). 使用形式 .cordll [Options] 参数 Options ...

  6. JavaScript操作BOM

    window对象的属性: history: 方法: back() 加载 history 对象列表中的前一个URL forward() 加载 history 对象列表中的下一个URL go() 加载 h ...

  7. 2015-2016-2《Java程序设计》团队博客1

    项目内容 经过一些讨论之后决定了最终的项目:简易画图板项目设计与开发 目标 制作一个能够画各种图形的画板,并能有多种颜色可以选择:输入文本时也可以选择字体:能够保存,新建,和导入图片 实现计划 十一周 ...

  8. Cannot capture jmeter traffic in fiddler

    Cannot capture jmeter traffic in fiddler First, change Fiddler's port back to 8888 as it was origina ...

  9. 【转】vue中样式被覆盖的问题,vue中的style不生效

    转载:http://www.cnblogs.com/shangjun6/p/11416054.html 在我们引入外部的样式时,发现自己无论如何都改不了外部的样式,自己的样式老被覆盖,究其原因还是我们 ...

  10. locust参数化(数据库取值)

    locust参数化(数据库取值) 基于上一篇参数化的梳理,本篇用另一种方法从数据库中取出这100个用户来登录 思路:在 TaskSet 中的 on_start 方法表示执行任务前的操作,可以将数据库取 ...