編輯:關於Android編程
這一篇主要根據上一篇的大致說明,我相信如果看完這一篇,對開發自定義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屬性就知道了.
我不知道大家有沒有這樣問題,項目做多了,就容易忽略最最基礎的知識,其實我也是在最近發現了自己也存在這樣的問題。因此打算做一些最基礎的知識的調研來重新學習和回顧這些容易被忽
搭建環境過程:1. 安裝JDK。注:實質上到該網址上下載好JDK安裝包,安裝後添加一個環境變量:JAVA_HOME,其值為:C:\Program Files\Java\j
原理最近用socket寫了一個消息推送的demo,在這裡和大家分享一下。主要實現了:一台手機向另外一台手機發送消息,這兩台手機可以隨時自由發送文本消息進行通信,類似我們常
上篇提到讓應用自動抓取Crash日志提交到服務器,如果發布之前沒有經過嚴格測試(個人練手做的一些小應用),會發現有很多異常都是顯而易見的錯誤,只是沒有嚴格測試忽略了而已,