这一篇主要根据上一篇的大致说明,我相信如果看完这一篇,对开发自定义View将会有很大的帮助,
先介绍ColorStateList和StateListDrawable两个类:
ColorStateList说明:https://developer.android.com/reference/android/content/res/ColorStateList.html
StateListDrawable说明:https://developer.android.com/reference/android/graphics/drawable/StateListDrawable.html
这两个共同的特点是根据状态的变化变换View的背景,ColorStateList一般是背景颜色更新.比如:
XML file saved at res/color/button_text.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:color="#ffff0000"/> <!-- pressed -->
<item android:state_focused="true"
android:color="#ff0000ff"/> <!-- focused -->
<item android:color="#ff000000"/> <!-- default -->
</selector>
然后在布局中使用:
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/button_text"
android:textColor="@color/button_text" />
这个地方都是android原生Button来完成解析Button_text.xml来更新父类View的背景/前景的调整,或者其他调整!这个是字体会随着点击变色.
如果是自定义的View,如何来设定这些操作了,下面看一看
<1> : 新建一个android studio工程:PumpKinDrawable:
主类程序:
package org.durian.pumpkindrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import org.durian.pumpkindrawable.view.ButtonColorDrawable;
import org.durian.pumpkindrawable.view.PumpKinDrawableView;
public class PumpKinMainActivity extends AppCompatActivity {
private ImageView imageView1;
private ButtonColorDrawable bcdrawable;
private PumpKinDrawableView pumpkinview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pump_kin_main);
imageView1=(ImageView)findViewById(R.id.imagestate);
bcdrawable=new ButtonColorDrawable();
imageView1.setBackground(bcdrawable);
imageView1.setClickable(true);
pumpkinview=(PumpKinDrawableView)findViewById(R.id.pumpkinview);
pumpkinview.setClickable(true);
}
}
对应布局文件:里面的图片自行给一张放到drawable中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="org.durian.pumpkindrawable.PumpKinMainActivity">
<org.durian.pumpkindrawable.view.PumpKinDrawableView
android:id="@+id/pumpkinview"
android:clickable="true"
android:layout_width="250dp"
android:layout_height="250dp" />
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/weather"/>
<ImageView
android:id="@+id/imagestate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/weather"/>
</LinearLayout>
那么自定义的View如下:
package org.durian.pumpkindrawable.view;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.Log;
/**
* Project name : PumpKinDrawable
* Created by zhibao.liu on 2016/4/29.
* Time : 14:58
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class ButtonColorDrawable extends Drawable {
private Paint mBGPaint;
private int[] mNoAnimationColor;
private ColorStateList mColorStateList;
private int[][] btStatus;
public ButtonColorDrawable() {
mNoAnimationColor = new int[]{Color.BLUE, Color.GREEN, Color.GRAY};
mBGPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBGPaint.setColor(Color.BLUE);
mBGPaint.setStrokeWidth(5);
mBGPaint.setStyle(Paint.Style.FILL);
mBGPaint.setAntiAlias(true);
//负号表示false,最后那个空数组呢,代表的是除开前面这两个状态以外的状态,这个一定要放到最后,不信你放到第一个试试有什么后果
btStatus = new int[][]{{-android.R.attr.state_pressed}, {android.R.attr.state_pressed}, {}};
//把前面创建好的状态对应的颜色素组塞到这个状态和颜色对应的队列里面
mColorStateList = new ColorStateList(btStatus, mNoAnimationColor);
}
/**
* Draw in its bounds (set via setBounds) respecting optional effects such
* as alpha (set via setAlpha) and color filter (set via setColorFilter).
*
* @param canvas The canvas to draw into
*/
@Override
public void draw(Canvas canvas) {
android.util.Log.i("pumpkin","draw ... ");
canvas.drawRoundRect(new RectF(getBounds()), 30, 30, mBGPaint);
}
/**
* 设置为true之后,drawable才能接受控件的状态
*
* @return
*/
@Override
public boolean isStateful() {
return true;
}
@Override
protected boolean onStateChange(int[] state) {
//当状态改变的时候,获取当前状态对应的颜色,这个颜色和状态的关系就是构造里面设置的那个
android.util.Log.i("pumpkin","onStateChange ... ");
int currentColor = mColorStateList.getColorForState(state, Color.WHITE);
mBGPaint.setColor(currentColor);
invalidateSelf();
return true;
}
/**
* Specify an alpha value for the drawable. 0 means fully transparent, and
* 255 means fully opaque.
*
* @param alpha
*/
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
这样运行结果:
点击前:
点击后:
昨天我们看了View的源代码,只要View被点击就会产生KeyEvent,最终调用:
protected void drawableStateChanged() {
Drawable d = mBackground;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
然后就会调用:
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
从而我们点击UI的时候就会执行ButtonColorDrawable的:
protected boolean onStateChange(int[] state)
在这个方法里面如果需要更新UI,则:
invalidateSelf();
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
在这里面回调调用刷新View视图.
刷新就开始调用draw方法:
@Override
public void draw(Canvas canvas) {
android.util.Log.i("pumpkin","draw ... ");
canvas.drawRoundRect(new RectF(getBounds()), 30, 30, mBGPaint);
}
从而实现背景颜色变化.
下面来看看StateListDrawable 如何实现背景变化的:在上面的工程添加下面的类:
package org.durian.pumpkindrawable.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import org.durian.pumpkindrawable.R;
/**
* Project name : PumpKinDrawable
* Created by zhibao.liu on 2016/4/29.
* Time : 17:09
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class PumpKinDrawableView extends View {
private Context mContext;
private Drawable mBackground;
private boolean mCanSizeChanged=true;
private Paint mPaint;
public PumpKinDrawableView(Context context) {
super(context);
initView(context);
}
public PumpKinDrawableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public PumpKinDrawableView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context);
}
public PumpKinDrawableView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public void initView(Context context){
mContext=context;
StateListDrawable statelistDrawable = new StateListDrawable();
int pressed = android.R.attr.state_pressed;
int windowfocused = android.R.attr.state_window_focused;
int enabled = android.R.attr.state_enabled;
int stateFoucesd = android.R.attr.state_focused;
statelistDrawable.addState(
new int[] { pressed, windowfocused },
mContext.getResources().getDrawable(
R.drawable.deskclock));
statelistDrawable.addState(new int[] { -pressed, windowfocused },
mContext.getResources()
.getDrawable(R.drawable.weather));
mBackground = statelistDrawable;
mBackground.setCallback(this);
setBackgroundDrawable(null);
mPaint=new Paint();
mPaint.setColor(Color.YELLOW);
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
android.util.Log.i("pumpkin","drawableStateChanged ... ");
Drawable d = mBackground;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
// drawbackground();
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
android.util.Log.i("pumpkin","verifyDrawable ... ");
return who == mBackground || super.verifyDrawable(who);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
android.util.Log.i("pumpkin","onDraw ... ");
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
android.util.Log.i("pumpkin","draw ... ");
if (mBackground != null) {
if (mCanSizeChanged) {
// 设置边界范围
mBackground.setBounds(0, 0, getRight() - getLeft(), getBottom()
- getTop());
mCanSizeChanged = false;
}
if ((getScrollX() | getScrollY()) == 0) // 是否偏移
{
mBackground.draw(canvas); // 绘制当前状态对应的图片
//canvas.drawCircle(250, 250, radio, mPaint);
} else {
canvas.translate(getScrollX(), getScrollY());
mBackground.draw(canvas); // 绘制当前状态对应的图片
canvas.translate(-getScrollX(), -getScrollY());
}
}
}
/*int radio=0;
int speechexpand=1;
boolean drawstatus=false;
public void drawbackground(){
if(drawstatus) {
}else{
drawstatus=true;
return;
}
radio=0;
if(task!=null){
if(!task.isCancelled()){
task.cancel(true);
}
}
task=new Task();
task.execute();
}
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
invalidate();
}
};
private Task task;
private class Task extends AsyncTask{
@Override
protected Object doInBackground(Object[] params) {
for(int i=0;i<30;i++) {
radio += speechexpand;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0);
}
return null;
}
};*/
}
同样,点击后
->drawableStateChanged
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
// drawbackground();
}
注意这里的setState引发状态变化,从而引发后面的View刷新操作,源码见上面的.
->verifyDrawable
->ondraw
->draw
经过这一路流程,View实现了背景刷新,运行效果:
点击前:
点击后:
这一篇一定要注意的地方是,所有的一切都是以程序逻辑方式更新背景的动画或者颜色,以及看清平时配置到xml中的背景是如何在程序中操纵的,
所以上面的图片都是背景,观者可以再设置imageView中xml的src属性就知道了.