本文原创,转载请注明出处:http://blog.csdn.net/qinjuning
今天继续给大家分享下View的相关知识,重点有一下两点:
1、View的几种不同状态属性
2、如何根据不同状态去切换我们的背景图片。
开篇介绍:android背景选择器selector用法汇总
对Android开发有经验的同学,对 节点的使用一定很熟悉,该节点的作用就是定义一组状态资源图片,使其能够
在不同的状态下更换某个View的背景图片。例如,如下的hello_selection.xml文件定义:
- "1.0" encoding="utf-8" ?>
- "http://schemas.android.com/apk/res/android">
-
-
- "true"
android:state_window_focused="true" android:drawable= "@drawable/pic1" />
-
-
- "true"
android:state_focused="false" android:drawable="@drawable/pic2" />
-
-
- "true"
android:drawable="@drawable/pic3" />
-
-
- "true"
android:drawable="@drawable/pic4" />
-
-
- "@drawable/pic5"
/>
-
更多关于 节点的使用请参考该博客<android背景选择器selector用法汇总>
其实,前面说的xml文件,最终会被Android框架解析成StateListDrawable类对象。
知识点一:StateListDrawable类介绍
类功能说明:该类定义了不同状态值下与之对应的图片资源,即我们可以利用该类保存多种状态值,多种图片资源。
常用方法为:
public void addState (int[] stateSet, Drawable drawable)
功能: 给特定的状态集合设置drawable图片资源
使用方式:参考前面的hello_selection.xml文件,我们利用代码去构建一个相同的StateListDrawable类对象,如下:
-
- StateListDrawable stalistDrawable = new StateListDrawable();
-
- int pressed = android.R.attr.state_pressed;
- int window_focused = android.R.attr.state_window_focused;
- int focused = android.R.attr.state_focused;
- int selected = android.R.attr.state_selected;
-
- stalistDrawable.addState(new int []{pressed , window_focused}, getResources().getDrawable(R.drawable.pic1));
- stalistDrawable.addState(new int []{pressed , -focused}, getResources().getDrawable(R.drawable.pic2);
- stalistDrawable.addState(new int []{selected }, getResources().getDrawable(R.drawable.pic3);
- stalistDrawable.addState(new int []{focused }, getResources().getDrawable(R.drawable.pic4);
-
- stalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.pic5);
上面的“-”负号表示对应的属性值为false
当我们为某个View使用其作为背景色时,会根据状态进行背景图的转换。
public boolean isStateful ()
功能: 表明该状态改变了,对应的drawable图片是否会改变。
注:在StateListDrawable类中,该方法返回为true,显然状态改变后,我们的图片会跟着改变。
知识点二:View的五种状态值
一般来说,Android框架为View定义了四种不同的状态,这些状态值的改变会引发View相关操作,例如:更换背景图片、是否
触发点击事件等;视
视图几种不同状态含义见下图:
其中selected和focused的区别有如下几点:
1,我们通过查看setSelected()方法,来获取相关信息。
SDK中对setSelected()方法----对于与selected状态有如下说明:
public void setSelected (boolean selected)
Since: APILevel 1
Changes the selection state of this view. Aview can be selected or not. Note that selection is not the same as
focus. Views are typically selected in the context of an AdapterView like ListView or GridView ;the selected view is
the view that is highlighted.
Parameters selected true if the view must be selected, false otherwise
由以上可知:selected不同于focus状态,通常在AdapterView类群下例如ListView或者GridView会使某个View处于
selected状态,并且获得该状态的View处于高亮状态。
2、一个窗口只能有一个视图获得焦点(focus),而一个窗口可以有多个视图处于”selected”状态中。
总结:focused状态一般是由按键操作引起的;
pressed状态是由触摸消息引起的;
selected则完全是由应用程序主动调用setSelected()进行控制。
例如:当我们触摸某个控件时,会导致pressed状态改变;获得焦点时,会导致focus状态变化。于是,我们可以通过这种
更新后状态值去更新我们对应的Drawable对象了。
问题:如何根据状态值的改变去绘制/显示对应的背景图?
当View任何状态值发生改变时,都会调用refreshDrawableList()方法去更新对应的背景Drawable对象。
其整体调用流程如下: View.java类中
-
-
-
-
-
-
- public void refreshDrawableState() {
- mPrivateFlags |= DRAWABLE_STATE_DIRTY;
-
- drawableStateChanged();
- ...
- }
-
-
-
-
- protected void drawableStateChanged() {
-
- Drawable d = mBGDrawable;
- if (d != null && d.isStateful()) {
-
-
- d.setState(getDrawableState());
- }
- }
-
-
-
- public final int[] getDrawableState() {
- if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
- return mDrawableState;
- } else {
-
- mDrawableState = onCreateDrawableState(0);
- mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
- return mDrawableState;
- }
- }
通过这段代码我们可以明白View内部是如何获取更细后的状态值以及动态获取对应的背景Drawable对象----setState()方法
去完成的。这儿我简单的分析下Drawable类里的setState()方法的功能,把流程给走一下:
Step 1 、 setState()函数原型 ,
函数位于:frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 类中
-
- public boolean setState(final int[] stateSet) {
- if (!Arrays.equals(mStateSet, stateSet)) {
- mStateSet = stateSet;
- return onStateChange(stateSet);
- }
- return false;
- }
该函数的主要功能: 判断状态值是否发生了变化,如果发生了变化,就调用onStateChange()方法进一步处理。
Step 2 、onStateChange()函数原型:
该函数位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 类中
-
- protected boolean onStateChange(int[] stateSet) {
-
- int idx = mStateListState.indexOfStateSet(stateSet);
- ...
-
- if (selectDrawable(idx)) {
- return true;
- }
- ...
- }
该函数的主要功能: 根据新的状态值,从StateListDrawable实例对象中,找到第一个完全吻合该新状态值的索引下标处 ;
继而,调用selectDrawable()方法去获取索引下标的当前Drawable对象。
具体查找算法在 mStateListState.indexOfStateSet(stateSet) 里实现了。基本思路是:查找第一个能完全吻合该新状态值
的索引下标,如果找到了,则立即返回。 具体实现过程,只好看看源码咯。
Step 3 、selectDrawable()函数原型:
该函数位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 类中
- public boolean selectDrawable(int idx)
- {
- if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
-
- Drawable d = mDrawableContainerState.mDrawables[idx];
- ...
- mCurrDrawable = d;
- mCurIndex = idx;
- ...
- } else {
- ...
- }
-
- invalidateSelf();
- return true;
- }
该函数的主要功能是选择当前索引下标处的Drawable对象,并保存在mCurrDrawable中。
知识点三: 关于Drawable.Callback接口
该接口定义了如下三个函数:
-
- public static interface Callback {
-
-
- public void invalidateDrawable(Drawable who);
-
- public void scheduleDrawable(Drawable who, Runnable what, long when);
-
- public void unscheduleDrawable(Drawable who, Runnable what);
- }
其中比较重要的函数为:
public voidinvalidateDrawable(Drawable who)
函数功能:如果Drawable对象的状态发生了变化,会请求View重新绘制,因此我们对应于该View的背景Drawable对象
能够重新”绘制“出来。
Android框架View类继承了该接口,同时实现了这三个函数的默认处理方式,其中invalidateDrawable()方法如下:
- public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource
- {
- ...
-
-
- public void invalidateDrawable(Drawable drawable) {
- if (verifyDrawable(drawable)) {
- final Rect dirty = drawable.getBounds();
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
-
- invalidate(dirty.left + scrollX, dirty.top + scrollY,
- dirty.right + scrollX, dirty.bottom + scrollY);
- }
- }
- ...
- }
因此,我们的Drawable类对象必须将View设置为回调对象,否则,即使改变了状态,也不会显示对应的背景图。 如下:
Drawable d ; // 图片资源
d.setCallback(View v) ; // 视图v的背景资源为 d 对象
知识点四:View绘制背景图片过程
在前面的博客中《Android中View绘制流程以及invalidate()等相关方法分析》,我们知道了一个视图的背景绘制过程时在
View类里的draw()方法里完成的,我们这儿在回顾下draw()的流程,同时重点讲解下绘制背景的操作。
-
-
- private void draw(Canvas canvas){
-
-
-
-
- if (!dirtyOpaque) {
-
- final Drawable background = mBGDrawable;
- if (background != null) {
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
-
- if (mBackgroundSizeChanged) {
-
- background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
- mBackgroundSizeChanged = false;
- }
-
- if ((scrollX | scrollY) == 0) {
- background.draw(canvas);
- } else {
-
-
- canvas.translate(scrollX, scrollY);
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
- }
- ...
-
-
-
-
- }
That's all ! 我们用到的知识点也就这么多吧。 如果大家有丝丝不明白的话,可以去看下源代码,具体去分析下这些流程到底
是怎么走下来的。
我们从宏观的角度分析了View绘制不同状态背景的原理,View框架就是这么做的。为了易于理解性,
下面我们通过一个小Demo来演示前面种种流程。
Demo 说明:
我们参照View框架中绘制不同背景图的实现原理,自定义一个View类,通过给它设定StateListDrawable对象,使其能够在
不同状态时能动态"绘制"背景图片。 基本流程方法和View.java类实现过程一模一样。
截图如下:
初始背景图 触摸后显示的背景图(pressed)
一、主文件MainActivity.java如下:
-
-
-
-
- public class MainActivity extends Activity
- {
-
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
-
- LinearLayout ll = new LinearLayout(MainActivity.this);
- CustomView customView = new CustomView(MainActivity.this);
-
- ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(200 , 100);
- customView.setLayoutParams(lp);
-
- customView.setClickable(true);
-
- ll.addView(customView);
- setContentView(ll);
- }
- }
功能很简单,为Activity设置了视图 。
二、 自定义View如下 , CustomView.java :
-
-
-
-
- public class CustomView extends View
- {
- private static String TAG = "TackTextView";
-
- private Context mContext = null;
- private Drawable mBackground = null;
- private boolean mBGSizeChanged = true;;
-
- public CustomView(Context context)
- {
- super(context);
- mContext = context;
- initStateListDrawable();
- }
-
-
- private void initStateListDrawable()
- {
-
-
- 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.btn_power_on_pressed));
- statelistDrawable.addState(new int[]{ -pressed, windowfocused },
- mContext.getResources().getDrawable(R.drawable.btn_power_on_nor));
-
- mBackground = statelistDrawable;
-
-
- mBackground.setCallback(this);
-
- this.setBackgroundDrawable(null);
-
-
-
- }
-
- protected void drawableStateChanged()
- {
- Log.i(TAG, "drawableStateChanged");
- Drawable d = mBackground;
- if (d != null && d.isStateful())
- {
- d.setState(getDrawableState());
- Log.i(TAG, "drawableStateChanged and is 111");
- }
-
- Log.i(TAG, "drawableStateChanged and is 222");
- super.drawableStateChanged();
- }
-
- protected boolean verifyDrawable(Drawable who)
- {
- return who == mBackground || super.verifyDrawable(who);
- }
-
- public void draw(Canvas canvas)
- {
- Log.i(TAG, " draw -----");
- if (mBackground != null)
- {
- if(mBGSizeChanged)
- {
-
- mBackground.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
- mBGSizeChanged = false ;
- }
- if ((getScrollX() | getScrollY()) == 0)
- {
- mBackground.draw(canvas);
- }
- else
- {
- canvas.translate(getScrollX(), getScrollY());
- mBackground.draw(canvas);
- canvas.translate(-getScrollX(), -getScrollY());
- }
- }
- super.draw(canvas);
- }
- public void onDraw(Canvas canvas) {
- ...
- }
- }
将该View设置的背景图片转换为节点xml,形式如下:
- "http://schemas.android.com/apk/res/android">
-
- "true"
- android:state_window_focused="true"
- android:drawable="@drawable/btn_power_on_pressed">
-
- "false"
- android:state_window_focused="true"
- android:drawable="@drawable/btn_power_on_nor">
-
-
基本上所有功能都在这儿显示出来了, 和我们前面说的一模一样吧。
当然了,如果你想偷懒,大可用系统定义好的一套工具 , 即直接使用setBackgroundXXX()或者在设置对应的属性,但是,
万变不离其宗,掌握了绘制原理,可以潇洒走江湖了。
示例Demo下载地址: http://download.csdn.net/detail/qinjuning/4237298
PS: 如果您觉得本文对您有用,请不忘顶一下 。 - - .
转载地址点击打开链接