android drawable 创建,[译]Android:自定义Drawable教程

#这篇教程一共分为三个部分。

1 Drawable与View

Drawable是什么?API文档的定义:A Drawable is a general abstraction for "something that can be drawn."。就是说Drawable表示这类可以被绘制的事物。

那么,如何使用,怎么把它添加到View上?我们来一步一步回答这个问题。

现在,我们有个需求,给图片添加边框,效果如下,

f3abe913c07f

border imageview

BorderDrawable

首先,我们创建一个Drawable的子类,并创建带参的构造器。

public class BorderDrawable extends Drawable {

Paint mPaint;

int mColor;

int mBorderWidth;

int mBorderRadius;

RectF mRect;

Path mPath;

public BorderDrawable(int color, int borderWidth, int borderRadius) {

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mPaint.setStyle(Paint.Style.FILL);

mPath = new Path();

mPath.setFillType(Path.FillType.EVEN_ODD);

mRect = new RectF();

mColor = color;

mBorderWidth = borderWidth;

mBorderRadius = borderRadius;

}

}

onBoundsChange(Rect)的时候计算mPath;

draw(Canvas)绘制mPath。

@Override protected void onBoundsChange(Rect bounds) {

mPath.reset();

mPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);

mRect.set(bounds.left + mBorderWidth, bounds.top + mBorderWidth, bounds.right - mBorderWidth, bounds.bottom - mBorderWidth);

mPath.addRoundRect(mRect, mBorderRadius, mBorderRadius, Path.Direction.CW);

}

@Override public void draw(Canvas canvas) {

mPaint.setColor(mColor);

canvas.drawPath(mPath, mPaint);

}

@Override public void setAlpha(int alpha) {

mPaint.setAlpha(alpha);

}

@Override public void setColorFilter(ColorFilter cf) {

mPaint.setColorFilter(cf);

}

@Override public int getOpacity() {

return PixelFormat.TRANSLUCENT;

}

BorderImageView

然后处理ImageView,

public class BorderImageView extends ImageView {

BorderDrawable mBorder;

public BorderImageView(Context context) {

super(context);

init(context, null, 0, 0);

}

public BorderImageView(Context context, AttributeSet attrs) {

super(context, attrs);

init(context, attrs, 0, 0);

}

//another constructors ...

private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

setWillNotDraw(false);

mBorder = new BorderDrawable(context.getResources().getColor(R.color.primary), getPaddingLeft(), getPaddingLeft() / 2);

}

}

上面调用setWillNotDraw(false)传入了false,保证自定义View会执行onDraw(canvas),否则不会执行。然后把ImageView的padding 值当作border的宽度。

然后重写onSizeChanged(int, int, int, int),设置drawable的尺寸。

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

mBorder.setBounds(0, 0, w, h);

}

然后在onDraw(canvas)里调用drawable的draw(Canvas)

Override protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

mBorder.draw(canvas);

}

最后,

android:layout_width="96dp"

android:layout_height="96dp"

android:src="@drawable/avatar"

android:scaleType="centerCrop"

android:padding="8dp"/>

其实,可以直接在ImageView 的onDraw(Canvas)里绘制边框,但是使用drawable便于复用。

2-创建带状态的drawable

现在,新需求来了,点击View边框颜色改变,效果如下,

f3abe913c07f

state-based

StateBorderDrawable

我们来改写BorderDrawable。

首先,把int color参数改成ColorStateList 。

public class StateBorderDrawable extends Drawable {

Paint mPaint;

ColorStateList mColorStateList;

int mColor;

int mBorderWidth;

int mBorderRadius;

RectF mRect;

Path mPath;

public BorderDrawable(ColorStateList colorStateList, int borderWidth, int borderRadius) {

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mPaint.setStyle(Paint.Style.FILL);

mPath = new Path();

mPath.setFillType(Path.FillType.EVEN_ODD);

mRect = new RectF();

mColorStateList = colorStateList;

mColor = mColorStateList.getDefaultColor();

mBorderWidth = borderWidth;

mBorderRadius = borderRadius;

}

}

isStateful()返回true,表明当view的状态改变的时候会通知这个Drawable。在onStateChange(int)方法里处理状态改变事件。

@Override

public boolean isStateful() {

return true;

}

@Override

protected boolean onStateChange(int[] state) {

int color = mColorStateList.getColorForState(state, mColor);

if(mColor != color){

mColor = color;

invalidateSelf();

return true;

}

return false;

}

如果当前drawable的颜色与view当前状态对应的颜色不一样,调用invalidateSelf()重新绘制。

StateBorderImageView

改写BorderImageView。

首先,init()方法:

private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){

setWillNotDraw(false);

int[][] states = new int[][]{

{-android.R.attr.state_pressed},

{android.R.attr.state_pressed}

};

int[] colors = new int[]{

context.getResources().getColor(R.color.primary),

context.getResources().getColor(R.color.accent)

};

ColorStateList colorStateList = new ColorStateList(states, colors);

mBorder = new StateBorderDrawable(colorStateList, getPaddingLeft(), getPaddingLeft() / 2);

mBorder.setCallback(this);

}

Drawable对象必须调用setCallback(Callback),才保证Drawable状态invalidated的时候,回调ImageView的重绘,进而重绘Drawable。

@Override

protected void drawableStateChanged() {

super.drawableStateChanged();

mBorder.setState(getDrawableState());

}

@Override

protected boolean verifyDrawable(Drawable dr) {

return super.verifyDrawable(dr) || dr == mBorder;

}

drawableStateChanged()当view状态改变,这个方法去通知drawable。

verifyDrawable()当drawable请求view重绘自己时(重绘是通过Callback的invalidateDrawable(Drawable)方法),view会先检查这个drawable是不是属于自己。

最后,

android:layout_width="96dp"

android:layout_height="96dp"

android:src="@drawable/avatar"

android:scaleType="centerCrop"

android:padding="8dp"/>

3-创建带动画的drawable

现在,新需求,逃不过的动画,

f3abe913c07f

animated drawable

AnimatedStateBorderDrawable

来改写StateBorderDrawable。

首先,我们需要一个duration参数。

public class AnimatedStateBorderDrawable extends Drawable {

private boolean mRunning = false;

private long mStartTime;

private int mAnimDuration;

Paint mPaint;

ColorStateList mColorStateList;

int mPrevColor;

int mMiddleColor;

int mCurColor;

int mBorderWidth;

int mBorderRadius;

RectF mRect;

Path mPath;

public BorderDrawable(ColorStateList colorStateList, int borderWidth, int borderRadius, int duration) {

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mPaint.setStyle(Paint.Style.FILL);

mPath = new Path();

mPath.setFillType(Path.FillType.EVEN_ODD);

mRect = new RectF();

mColorStateList = colorStateList;

mCurColor = mColorStateList.getDefaultColor();

mPrevColor = mCurColor;

mBorderWidth = borderWidth;

mBorderRadius = borderRadius;

mAnimDuration = duration;

}

}

添加了一些新变量,比如mPrevColor,mCurColor,mMiddeColor。需要知道之前和当前状态的颜色值,才能在两个状态之间添加颜色动画。变量mRunning,mStartTime为了记录动画数据。

然后实现android.graphics.drawable.Animatable接口,重写3个方法。

@Override

public boolean isRunning() {

return mRunning;

}

@Override

public void start() {

resetAnimation();

scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);

invalidateSelf();

}

@Override

public void stop() {

mRunning = false;

unscheduleSelf(mUpdater);

invalidateSelf();

}

调用start()开始动画。3行代码干3件事。

先重置动画数据,mStartTime记录动画开始时间,mMiddleColor记录动画执行过程中绘制的颜色。

然后scheduleSelf ()方法,将在指定的时间执行第一个参数Runnable。

invalidateSelf()使Drawable状态invalidated,这会通知Callback。

private void resetAnimation(){

mStartTime = SystemClock.uptimeMillis();

mMiddleColor = mPrevColor;

}

private final Runnable mUpdater = new Runnable() {

@Override

public void run() {

update();

}

};

private void update(){

long curTime = SystemClock.uptimeMillis();

float progress = Math.min(1f, (float) (curTime - mStartTime) / mAnimDuration);

mMiddleColor = getMiddleColor(mPrevColor, mCurColor, progress);

if(progress == 1f)

mRunning = false;

if(isRunning())

scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);

invalidateSelf();

}

update()方法,通过动画进度和两个状态颜色值计算mMiddleColor,然后根据动画是否执行完毕来决定是否继续安排任务mUpdater。

@Override

protected boolean onStateChange(int[] state) {

int color = mColorStateList.getColorForState(state, mCurColor);

if(mCurColor != color){

if(mAnimDuration > 0){

mPrevColor = isRunning() ? mMiddleColor : mCurColor;

mCurColor = color;

start();

}

else{

mPrevColor = color;

mCurColor = color;

invalidateSelf();

}

return true;

}

return false;

}

@Override

public void draw(Canvas canvas) {

mPaint.setColor(isRunning() ? mMiddleColor : mCurColor);

canvas.drawPath(mPath, mPaint);

}

@Override

public void jumpToCurrentState() {

super.jumpToCurrentState();

stop();

}

@Override

public void scheduleSelf(Runnable what, long when) {

mRunning = true;

super.scheduleSelf(what, when);

}

当view想让drawable无动画直接转变状态时,jumpToCurrentState()会被调用,所以我们stop()动画。

AnimatedStateBorderImageView

改写StateBorderImageView.

mBorder = new AnimatedStateBorderDrawable(colorStateList,

getPaddingLeft(),

getPaddingLeft() / 2,

context.getResources().getInteger(android.R.integer.config_mediumAnimTime));

@Override

public void jumpDrawablesToCurrentState() {

super.jumpDrawablesToCurrentState();

mBorder.jumpToCurrentState();

}

jumpDrawablesToCurrentState()通知drawable状态改变。

android:layout_width="96dp"

android:layout_height="96dp"

android:src="@drawable/avatar"

android:scaleType="centerCrop"

android:padding="8dp"/>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值