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和图片中长的那一个。
这是因为我们设置了默认宽高为bitmap的宽高,如果我们不去设置默认宽高,让其返回-1,效果将是下面这个样子:
就变成了text的高度和宽度了。
总结:
-
当使用setImageDrawable(drawable)函数来设置ImageView数据源时,自定义Drawable的位置和大小与ImageView的scaleType有关
-
当使用setBackgroundDrawable(drawable)函数来设置View的背景时,自定义的Drawable的宽高与控件大小一致,控件的宽、高则选取本身宽高和自定义Drawable的宽高。
自定义Drawable和自定义View差别很大,虽然像是砍掉手势交互的自定义View,但是自定义Drawable的使用场景非常明确,就是使用在有Drawable的地方中。而且也可以替代Bimap用于View中(比如放大镜效果
既然它可以替代Bitmap,那我们就来讲一下它和bitmap之间的关系吧。
6、Drawable与Bitmap的对比
(1)定义对比
-
Bitmap是位图,bmp格式,编码器很多,有RGB_565,ARGB_8888,逐像素的显示对象,执行效率很高,但是存储效率低下
-
Drawable作为Andorid下通用的图形对象,可以装载常用格式的图像,比如GIF PNG JPG BMP,还提供了高级可视化对象,比如渐变、图形
所以Bitmap是Drawable,但是Drawable不一定是Bitmap。
(2)指标对比
单是从占用内存这一点上,我们就有必要在使用Bitmap一定要考虑可不可以使用Drawable了,绘制速度更是如此。
所以在Android UI系统中普遍使用Drawable。
(3)绘制便利性对比:
Drawable有很多派生类,通过这些派生类可以容易地生成渐变、层叠等效果。但从这一方面而言,Drawable比Bitmap更有优势。如果仅仅用空白画布来绘图,drawable构造和使用则不如bitmap方便。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
分享读者
作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。
被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!
我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。
主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。
如果你觉得自己学习效率低,缺乏正确的指导,可以点击加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
份进了阿里一直到现在。
被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!
我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。
主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。
[外链图片转存中…(img-hsYfQQYG-1711049343414)]
如果你觉得自己学习效率低,缺乏正确的指导,可以点击加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。