Android自定义控件开发入门与实战(13)Android画布,通用流行框架大全

我们还是通过改变那行初始化的代码来构造一个扇形:

shapeDrawable = new ShapeDrawable(new ArcShape(0, 300));

效果如下:

在这里插入图片描述

RoundShape

字面上是实现一个圆角矩形,但它不仅可以实现圆角矩形,其本意是实现镂空的圆角矩形。而且内角也可以设置圆角

其构造函数为:

public RoundRectShape(float[] outerRadii, RectF inset , float[] innerRadii)

参数

  • float[] outerRadii:外围矩形的各个角度大小,需要填充8个数字,每两个数字一组,分别对应 左上角、右上角、左下角、右下角 4个角度,每两个一组的数字构成了一个椭圆,第一个数表示x轴半径,第二个轴表示y轴半径。不想指定圆角时,可以填null

  • RectF inset:表示内部矩形与外部矩形的各边边距,4个值表示四条边的距离

  • float[] innerRadii:表示内部矩形的各个角度的大小,同样需要填充8个数字。含义和outerRadii一样。

举一个例子:

。。。

float[] outerR = new float[]{12, 12, 12, 12, 0, 0, 0, 0};

RectF inset = new RectF(6, 12, 15, 3);

float[] innerR = new float[]{50, 12, 0, 0, 12, 50, 0, 0};

shapeDrawable = new ShapeDrawable(new RoundRectShape(outerR, inset, innerR));

。。。

其他的代码都不变,得到的效果如下:

在这里插入图片描述

PathShape

其构造函数如下:

public PathShape(Path path, float stdWidht,float stdHeight)

其中path表示要画的路径。

stdWidth表示标准宽度,即将整个ShapeDrawable的宽度分成多少份,Path中的moveTo(x,y)、line(x2,2)这些函数中的数值在这里其实都是以每一份的位置来计算的。当ShapeDrawable动态变大、变小时,每一份都会变小,而根据这些份的数值画出来的Path图形就会动态缩放。

实现一个实例的代码如下:

public ShapeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setLayerType(LAYER_TYPE_SOFTWARE, null);

Path path = new Path();

path.moveTo(0, 0);

path.lineTo(100, 0);

path.lineTo(100, 100);

path.lineTo(0, 100);

path.close();

shapeDrawable = new ShapeDrawable(new PathShape(path, 100, 100));

shapeDrawable.setBounds(new Rect(0, 0, 250, 150));

shapeDrawable.getPaint().setColor(Color.RED);

}

为了验证份成n份的概念,这边直接把drawable的大小设置成和控件大小一样了。就跟代码path描述一样,drawable会把控件填满。

效果如下:

在这里插入图片描述

这个时候如果我们把ShapeDrawable的高度份数改成200,那么同样的路径代码:

shapeDrawable = new ShapeDrawable(new PathShape(path, 100, 200));

高度就变成了原来的一半了。

在这里插入图片描述

自定义Shape

除了系统自带的Shape,我们还可以自定义Shape啦,就是继承Shape类,重写其draw方法。

这个实例就不讲了。以后有兴趣了再来实现。

3、常用函数

(1)、setBounds():指定当前ShapeDrawable在该控件的显示位置

(2)、getPaint():获取ShapeDrawable的paint,然后我们可以实现paint的所有函数

这里有一点,关于Shader,在之前讲shader的时候我们知道Shader是从画布左上角开始绘制的,所以在ShapeDrawable中,给其设置了Shader,也会从ShapeDrawable左上角开始绘制

(3)、其他的函数包括

setAlpha(int alpha)

setColorFilter(ColorFilter colorFilter):设置ColorFilter

setIntrinsicHeight(int height):设置默认高度。当Drawable以setbackGroundDrawable及setImageDrawable方式使用时,会使用默认宽度和默认高度来计算当前Drawable的大小与位置。如果不设置默认的宽高都是-1px。

setIntrinsicWidth(int width):设置默认宽度

setPadding(Rect padding):设置边距

4、放大镜效果

首先看下onDraw函数:

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (bitmap == null) {

Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.bg_telescope);

bitmap = bmp.createScaledBitmap(bmp, getWidth(), getHeight(), false);

BitmapShader bmShader = new BitmapShader(Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() * FACTOR, bitmap.getHeight() * FACTOR, true),

Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

drawable = new ShapeDrawable(new OvalShape());

drawable.getPaint().setShader(bmShader);

drawable.setBounds(0, 0, RADIUS * 2, RADIUS * 2);

}

canvas.drawBitmap(bitmap,0,0,null);

drawable.draw(canvas);

}

(1)为什么我们要把bitmap的初始化放在 onDraw方法中,因为我们需要把被放大的图片缩放到控件大小。而控件的大小只有在onLayout之后才知道,所以就在onDraw来拿,至于Bitmap.createScaledBitmap()之后再讲。

(2)创建ShapeDrawable,做的是放大镜形状,所以我们要构造OvalShape,然后大小是事先设定好的radius*2

(3)bmShader就是创建一个将原图放大三倍的图片(因为放大镜放大三倍)

接下来看下onTouchEvent代码:

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_MOVE:

case MotionEvent.ACTION_DOWN:

final int x = (int) event.getX();

final int y = (int) event.getY();

// 这个位置表示的是绘制Shader 的起始位置

matrix.setTranslate(RADIUS - x * FACTOR, RADIUS - y * FACTOR);

drawable.getPaint().getShader().setLocalMatrix(matrix);

drawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS);

invalidate();

return true;

case MotionEvent.ACTION_UP:

drawable.setBounds(0,0,0,0);

invalidate();

return super.onTouchEvent(event);

default:

return super.onTouchEvent(event);

}

}

在onTouchEvent中返回了true,表示完全拦截了所有Touch事件,因为这里完全不需要使用View默认的处理行为。

当手指有动作的时候我们应该改变当前ShapeDrawable的位置

drawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS);

即以当前手指位置为中心,画一个圆。

最关键的是Shader如何移动到我们要显示的位置,我们讲过,Shader的开始显示位置在ShapeDrawable的左上角。所以,如果我们不移动Shape,那么显示出来的永远是图片的左上角部分。

我们需要先找到当前手指放大3倍的图片上对应的点,然后以这个对应点为中心显示出半径为RADIUS的圆中的图形。

对应的点好找,当前手指的位置是(x,y),那么放大的位置是(3x,3y)。为了显示以放大3倍后的手指位置为中心的圆形区域,BitmapShader需要向左和上移动多少呢?

其实画个图就知道 是向左移动 (-3x+r) 向上移动 (-3y+r)

效果如下所示:

在这里插入图片描述

5、自定义Drawable

自定义控件时Android控件的精髓,所以Drawable系统自带的api(ShapeDrawable GradientDrawable)大部分都满足不了我们的需求,所以我们更需要自己学习做,这里来自定义一个Drawable。

让类继承自Drawable,可以看到必须要实现以下抽象函数:

public class CustomDrawable extends Drawable {

@Override

public void draw(@NonNull Canvas canvas) {

}

@Override

public void setAlpha(int alpha) {

}

@Override

public void setColorFilter(@Nullable ColorFilter colorFilter) {

}

@Override

public int getOpacity() {

return 0;

}

}

4个函数意义如下:

  • draw():使我们将会用到的,与View类似,传入的参数是一个Canvas对象,我们只需要调用Canvas的一些方法,效果就会直接显示在Drawable上

  • setAlpha()和setColorFilter()函数非常容易实现。当外层调用Drawable这两个函数时,我们只需要将对应的参数设置给Drawable的Paint即可。

  • getOpacity():当外部需要知道我们自定义的Drawable显示模式时会调用这个函数。它有4个取值:PixelFormat.UNKNOWN,TRANSLUCENT,TRANSPARENT,OPAQUE。其中PexelFormat.UNKNOWN表示当前Drawable的绘图是具有Alpha通道的,即使用Drawable后,其底部的图像仍有可能看得到。PixelFormat.TRANSPARENT表示当前Drawable是完全透明的。PexelFormat.OPAQUE表示当前图像是完全没有Alpha通道的,使用Drawable后,其底层图像将完全被覆盖。PixelFormal.UNKNOWN表示未知。一般而言,如果我们不知道怎么返回,直接返回PixelFormat.TRANSLUCENT是最靠谱的做法。

我们来自定义一个圆角矩形Drawable:

public class CustomDrawable extends Drawable {

private Paint mPaint;

private Bitmap mBitmap;

private BitmapShader bitmapShader;

private RectF mBounds;

public CustomDrawable(Bitmap mBitmap) {

this.mBitmap = mBitmap;

mPaint = new Paint();

mPaint.setAntiAlias(true);

}

@Override

public void draw(@NonNull Canvas canvas) {

canvas.drawRoundRect(mBounds, 20, 20, mPaint);

}

@Override

public void setAlpha(int alpha) {

mPaint.setAlpha(alpha);

}

@Override

public void setColorFilter(@Nullable ColorFilter colorFilter) {

mPaint.setColorFilter(colorFilter);

}

@Override

public int getOpacity() {

return PixelFormat.TRANSLUCENT;

}

@Override

public void setBounds(int left, int top, int right, int bottom) {

super.setBounds(left, top, right, bottom);

bitmapShader = new BitmapShader(Bitmap.createScaledBitmap(mBitmap, right - left, bottom - top, true), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

mPaint.setShader(bitmapShader);

mBounds = new RectF(left, top, right, bottom);

}

@Override

public int getIntrinsicWidth() {

return mBitmap.getWidth();

}

@Override

public int getIntrinsicHeight() {

return mBitmap.getHeight();

}

}

我们在getIntrinsicWidth/Height设置了默认的宽高

setBounds()在ShapeDrawable中我们就接触了这个函数,含义是给Drawable设定边界,即这块Drawable画布的大小。在setBounds()函数中,我们根据边界创建一个与Drawable相同大小的Bitmap作为Drawable的Shader。

也就是说Bitmap会根据Drawable的大小动态拉伸,以完全覆盖这个Drawable,最后将边界保存起来,以便在绘图中使用。

draw:我们知道,Sader始终是从画布的左上角开始平铺的,而drawXXX函数只用来指定哪部分显示出来, 所以我们只需要在draw方法中调用drawRoundRect函数就能将BitmapShder以圆角矩形的方式显示出来即可。

Drawable的使用方法

一般有两种使用方法,一种是通过ImageView的setImageDrawable(drawable)函数将其设置为ImageView的源图片,另外一种是通过View的setBackgroundDrawable(drawable)函数将其作为背景

<ImageView

android:id="@+id/iv"

android:layout_width=“200dp”

android:layout_height=“200dp”

android:background="#ffffff"

android:scaleType=“fitXY”/>

然后代码中通过:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.avator_xizuka);

iv = findViewById(R.id.iv);

CustomDrawable drawable = new CustomDrawable(bitmap);

iv.setImageDrawable(drawable);

在这里插入图片描述

这就跟直接在ImageView中直接设置src一样。

假如我么能通过setBackgroundDrawable来设置,并且在xml文件中将ImageView改成TextView,宽高改成wrap_content,效果如下:

在这里插入图片描述

这个时候图片被拉得很长,宽度 高度是取textView和图片中长的那一个。
awable.avator_xizuka);

iv = findViewById(R.id.iv);

CustomDrawable drawable = new CustomDrawable(bitmap);

iv.setImageDrawable(drawable);

在这里插入图片描述

这就跟直接在ImageView中直接设置src一样。

假如我么能通过setBackgroundDrawable来设置,并且在xml文件中将ImageView改成TextView,宽高改成wrap_content,效果如下:

在这里插入图片描述

这个时候图片被拉得很长,宽度 高度是取textView和图片中长的那一个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
封面 1 序 2 捐助说明 5 目 录 7 第一章 View的绘图流程 12 1.1、概述 12 1.2、Activity的组成结构 13 1.3、View树的绘图流程 15 1.3.1 测量组件大小 16 1.3.2 确定子组件的位置 17 1.3.3 绘制组件 18 1.4、说点别的 22 1.5 练习作业 22 第二章 Graphics2D API 23 2.1、概述 23 2.2、Point类和PointF类 23 2.3、Rect类和RectF类 25 2.4、Bitmap类和BitmapDrawable类 32 2.5、Canvas类与Paint类 34 2.5.1 绘图概述 34 2.5.2 Paint类 34 2.5.3 Canvas类 39 2.6 练习作业 63 第三章 使用Graphics2D实现动态效果 64 3.1 概述 64 3.2 invalidate()方法 65 3.3 坐标转换 69 3.4 剪切区(Clip) 73 3.5 案例:指针走动的手表 82 3.6 练习作业 88 第四章 双缓存技术 89 4.1 双缓存 89 4.2 在屏幕上绘制曲线 90 4.3 在屏幕上绘制矩形 99 4.4 案例:绘图App 104 4.4.1 绘图属性 106 4.4.2 软件参数 108 4.4.3 绘图缓冲区 109 4.4.4 撤消操作 111 4.4.5 图形绘制 113 4.4.6 绘图区 118 4.4.7 主界面 119 4.5 练习作业 122 第五章 阴影、渐变和位图运算 123 5.1 概述 123 5.2 阴影 123 5.3 渐变 125 5.3.1 线性渐变(LinearGradient) 126 5.3.2 径向渐变(RadialGradient) 130 5.3.3 扫描渐变(SweepGradient) 135 5.3.4 位图渐变(BitmapShader) 138 5.3.5 混合渐变(ComposeShader) 140 5.3.6 渐变与Matrix 142 5.4 位图运算 143 5.4.1 PorterDuffXfermode 143 5.4.2 图层(Layer) 146 5.4.3 位图运算技巧 148 5.5 案例1:圆形头像 152 5.6 案例2:刮刮乐 156 5.7 练习作业 161 第六章 自定义组件 163 6.1 概述 163 6.2 自定义组件的基本结构 164 6.3 重写onMeasure方法 166 6.4 组件属性 175 6.4.1 属性的基本定义 175 6.4.2 读取来自style和theme中的属性 181 6.5 案例1:圆形ImageView组件 186 6.6 案例2:验证码组件CodeView 190 6.7 练习作业 202 第七章 自定义容器 204 7.1 概述 204 7.2 ViewGroup类 205 7.2.1 ViewGroup常用方法 205 7.2.2 ViewGroup的工作原理 208 7.2.3 重写onLayout()方法 213 7.3 CornerLayout布局 217 7.3.1 基本实现 217 7.3.2 内边距padding 224 7.3.3 外边距margin 228 7.3.4 自定义LayoutParams 238 7.4 案例:流式布局(FlowLayout) 246 7.5 练习作业 256 第八章 Scroller与平滑滚动 257 8.1 概述 257 8.2 认识scrollTo()和scrollBy()方法 258 8.3 Scroller类 264 8.4 平滑滚动的工作原理 271 8.5 案例:触摸滑屏 272 8.5.1 触摸滑屏的技术分析 272 8.5.2 速度跟踪器VelocityTracker 273 8.5.3 触摸滑屏的分步实现 274 8.6 练习作业 285 第九章 侧边栏 287 9.1 概述 287 9.2 使用二进制保存标识数据 289 9.2.1 位运算符 289 9.2.2 位运算的常用功能 292 9.3 继承自ViewGroup的侧边栏 293 9.4 继承自HorizontalScrollView的侧边栏 304 9.5 练习作业 312 第十章 加强版ListView 313 10.1 概述 313 10.2 ListView的基本使用 314 10.3 ListItem随手指左右滑动 318 10.4 向右滑动删除ListItem 326 10.5 滑动ListItem出现删除按钮 336 10.5.1 列表项专用容器ExtendLayout 337 10.5.2 列表项能滑出删除按钮的ListView 342 10.5.3 定义布局文件 350 10.5.4 显示ListView 351 10.6练习作业 353 案例代码说明 354

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值