這一篇主要根據上一篇的大致說明,我相信如果看完這一篇,對開發自定義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 [email protected]
* 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 [email protected]
* 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屬性就知道了.