Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解

本文详细介绍了Android中使用StateListDrawable实现视图背景切换的方法。通过分析StateListDrawable的工作原理及View的状态属性,展示了如何根据不同的状态动态更改背景图片。
          今天继续给大家分享下View的相关知识,重点有一下两点:


           1、View的几种不同状态属性

           2、如何根据不同状态去切换我们的背景图片。

 

 

开篇介绍:android背景选择器selector用法汇总


        对Android开发有经验的同学,对 <selector>节点的使用一定很熟悉,该节点的作用就是定义一组状态资源图片,使其能够

  在不同的状态下更换某个View的背景图片。例如,如下的hello_selection.xml文件定义:

  1. <?xml version="1.0" encoding="utf-8" ?>     
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">   
  3.   <!-- 触摸时并且当前窗口处于交互状态 -->    
  4.   <item android:state_pressed="true" android:state_window_focused="true" android:drawable= "@drawable/pic1" />  
  5.   <!--  触摸时并且没有获得焦点状态 -->    
  6.   <item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/pic2" />    
  7.   <!--选中时的图片背景-->    
  8.   <item android:state_selected="true" android:drawable="@drawable/pic3" />     
  9.   <!--获得焦点时的图片背景-->    
  10.   <item android:state_focused="true" android:drawable="@drawable/pic4" />    
  11.   <!-- 窗口没有处于交互时的背景图片 -->    
  12.   <item android:drawable="@drawable/pic5" />   
  13. </selector>  
<?xml version="1.0" encoding="utf-8" ?>   
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
  <!-- 触摸时并且当前窗口处于交互状态 -->  
  <item android:state_pressed="true" android:state_window_focused="true" android:drawable= "@drawable/pic1" />
  <!--  触摸时并且没有获得焦点状态 -->  
  <item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/pic2" />  
  <!--选中时的图片背景-->  
  <item android:state_selected="true" android:drawable="@drawable/pic3" />   
  <!--获得焦点时的图片背景-->  
  <item android:state_focused="true" android:drawable="@drawable/pic4" />  
  <!-- 窗口没有处于交互时的背景图片 -->  
  <item android:drawable="@drawable/pic5" /> 
</selector>

           更多关于 <selector>节点的使用请参考该博客<android背景选择器selector用法汇总>


       其实,前面说的xml文件,最终会被Android框架解析成StateListDrawable类对象。

 

 

知识点一:StateListDrawable类介绍


    类功能说明:该类定义了不同状态值下与之对应的图片资源,即我们可以利用该类保存多种状态值,多种图片资源。

    常用方法为:

       public void addState (int[] stateSet, Drawable drawable)

       功能: 给特定的状态集合设置drawable图片资源

       使用方式:参考前面的hello_selection.xml文件我们利用代码去构建一个相同的StateListDrawable类对象,如下:

  1. //初始化一个空对象   
  2. StateListDrawable stalistDrawable = new StateListDrawable();  
  3. //获取对应的属性值 Android框架自带的属性 attr   
  4. int pressed = android.R.attr.state_pressed;  
  5. int window_focused = android.R.attr.state_window_focused;  
  6. int focused = android.R.attr.state_focused;  
  7. int selected = android.R.attr.state_selected;  
  8.   
  9. stalistDrawable.addState(new int []{pressed , window_focused}, getResources().getDrawable(R.drawable.pic1));  
  10. stalistDrawable.addState(new int []{pressed , -focused}, getResources().getDrawable(R.drawable.pic2);  
  11. stalistDrawable.addState(new int []{selected }, getResources().getDrawable(R.drawable.pic3);  
  12. stalistDrawable.addState(new int []{focused }, getResources().getDrawable(R.drawable.pic4);  
  13. //没有任何状态时显示的图片,我们给它设置我空集合   
  14. stalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.pic5);  
//初始化一个空对象
StateListDrawable stalistDrawable = new StateListDrawable();
//获取对应的属性值 Android框架自带的属性 attr
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类中

  1. //路径:\frameworks\base\core\java\android\view\View.java   
  2.     /* Call this to force a view to update its drawable state. This will cause 
  3.      * drawableStateChanged to be called on this view. Views that are interested 
  4.      * in the new state should call getDrawableState. 
  5.      */   
  6.     //主要功能是根据当前的状态值去更换对应的背景Drawable对象   
  7.     public void refreshDrawableState() {  
  8.         mPrivateFlags |= DRAWABLE_STATE_DIRTY;  
  9.         //所有功能在这个函数里去完成   
  10.         drawableStateChanged();  
  11.         ...  
  12.     }  
  13.     /* This function is called whenever the state of the view changes in such 
  14.      * a way that it impacts the state of drawables being shown. 
  15.      */  
  16.     // 获得当前的状态属性--- 整型集合 ; 调用Drawable类的setState方法去获取资源。   
  17.     protected void drawableStateChanged() {  
  18.         //该视图对应的Drawable对象,通常对应于StateListDrawable类对象   
  19.         Drawable d = mBGDrawable;     
  20.         if (d != null && d.isStateful()) {  //通常都是成立的   
  21.             //getDrawableState()方法主要功能:会根据当前View的状态属性值,将其转换为一个整型集合   
  22.             //setState()方法主要功能:根据当前的获取到的状态,更新对应状态下的Drawable对象。   
  23.             d.setState(getDrawableState());  
  24.         }  
  25.     }  
  26.     /*Return an array of resource IDs of the drawable states representing the 
  27.      * current state of the view. 
  28.      */  
  29.     public final int[] getDrawableState() {  
  30.         if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {  
  31.             return mDrawableState;  
  32.         } else {  
  33.             //根据当前View的状态属性值,将其转换为一个整型集合,并返回   
  34.             mDrawableState = onCreateDrawableState(0);  
  35.             mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;  
  36.             return mDrawableState;  
  37.         }  
  38.     }  
//路径:\frameworks\base\core\java\android\view\View.java
    /* Call this to force a view to update its drawable state. This will cause
     * drawableStateChanged to be called on this view. Views that are interested
     * in the new state should call getDrawableState.
     */ 
    //主要功能是根据当前的状态值去更换对应的背景Drawable对象
    public void refreshDrawableState() {
        mPrivateFlags |= DRAWABLE_STATE_DIRTY;
        //所有功能在这个函数里去完成
        drawableStateChanged();
        ...
    }
    /* This function is called whenever the state of the view changes in such
     * a way that it impacts the state of drawables being shown.
     */
    // 获得当前的状态属性--- 整型集合 ; 调用Drawable类的setState方法去获取资源。
    protected void drawableStateChanged() {
    	//该视图对应的Drawable对象,通常对应于StateListDrawable类对象
        Drawable d = mBGDrawable;   
        if (d != null && d.isStateful()) {  //通常都是成立的
        	//getDrawableState()方法主要功能:会根据当前View的状态属性值,将其转换为一个整型集合
        	//setState()方法主要功能:根据当前的获取到的状态,更新对应状态下的Drawable对象。
            d.setState(getDrawableState());
        }
    }
    /*Return an array of resource IDs of the drawable states representing the
     * current state of the view.
     */
    public final int[] getDrawableState() {
        if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
            return mDrawableState;
        } else {
        	//根据当前View的状态属性值,将其转换为一个整型集合,并返回
            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 类中


  1. //如果状态态值发生了改变,就回调onStateChange()方法。   
  2. public boolean setState(final int[] stateSet) {  
  3.     if (!Arrays.equals(mStateSet, stateSet)) {  
  4.         mStateSet = stateSet;  
  5.         return onStateChange(stateSet);  
  6.     }  
  7.     return false;  
  8. }  
    //如果状态态值发生了改变,就回调onStateChange()方法。
    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 类中


  1. //状态值发生了改变,我们需要找出第一个吻合的当前状态的Drawable对象   
  2. protected boolean onStateChange(int[] stateSet) {  
  3.     //要找出第一个吻合的当前状态的Drawable对象所在的索引位置, 具体匹配算法请自己深入源码看看   
  4.     int idx = mStateListState.indexOfStateSet(stateSet);  
  5.     ...  
  6.     //获取对应索引位置的Drawable对象   
  7.     if (selectDrawable(idx)) {  
  8.         return true;  
  9.     }  
  10.     ...  
  11. }  
    //状态值发生了改变,我们需要找出第一个吻合的当前状态的Drawable对象
    protected boolean onStateChange(int[] stateSet) {
    	//要找出第一个吻合的当前状态的Drawable对象所在的索引位置, 具体匹配算法请自己深入源码看看
        int idx = mStateListState.indexOfStateSet(stateSet);
        ...
        //获取对应索引位置的Drawable对象
        if (selectDrawable(idx)) {
            return true;
        }
        ...
    }

          该函数的主要功能: 根据新的状态值,从StateListDrawable实例对象中,找到第一个完全吻合该新状态值的索引下标处 ;

   继而,调用selectDrawable()方法去获取索引下标的当前Drawable对象。

         具体查找算法在 mStateListState.indexOfStateSet(stateSet) 里实现了。基本思路是:查找第一个能完全吻合该新状态值

   的索引下标,如果找到了,则立即返回。 具体实现过程,只好看看源码咯。

  

       Step 3 、selectDrawable()函数原型:

            该函数位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 类中

  1. public boolean selectDrawable(int idx)  
  2. {  
  3.     if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {  
  4.         //获取对应索引位置的Drawable对象   
  5.         Drawable d = mDrawableContainerState.mDrawables[idx];  
  6.         ...  
  7.         mCurrDrawable = d; //mCurrDrawable即使当前Drawable对象   
  8.         mCurIndex = idx;  
  9.         ...  
  10.     } else {  
  11.        ...  
  12.     }  
  13.     //请求该View刷新自己,这个方法我们稍后讲解。   
  14.     invalidateSelf();  
  15.     return true;  
  16. }  
    public boolean selectDrawable(int idx)
    {
        if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
        	//获取对应索引位置的Drawable对象
            Drawable d = mDrawableContainerState.mDrawables[idx];
            ...
            mCurrDrawable = d; //mCurrDrawable即使当前Drawable对象
            mCurIndex = idx;
            ...
        } else {
           ...
        }
        //请求该View刷新自己,这个方法我们稍后讲解。
        invalidateSelf();
        return true;
    }

             该函数的主要功能是选择当前索引下标处的Drawable对象,并保存在mCurrDrawable中。



知识点三: 关于Drawable.Callback接口

   

    该接口定义了如下三个函数:     

  1. //该函数位于 frameworks\base\graphics\java\android\graphics\drawable\Drawable.java 类中   
  2. public static interface Callback {  
  3.     //如果Drawable对象的状态发生了变化,会请求View重新绘制,   
  4.     //因此我们对应于该View的背景Drawable对象能够”绘制出来”.   
  5.     public void invalidateDrawable(Drawable who);  
  6.     //该函数目前还不懂   
  7.     public void scheduleDrawable(Drawable who, Runnable what, long when);  
  8.      //该函数目前还不懂   
  9.     public void unscheduleDrawable(Drawable who, Runnable what);  
  10. }  
    //该函数位于 frameworks\base\graphics\java\android\graphics\drawable\Drawable.java 类中
    public static interface Callback {
    	//如果Drawable对象的状态发生了变化,会请求View重新绘制,
    	//因此我们对应于该View的背景Drawable对象能够”绘制出来”.
        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()方法如下:

  1. public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource   
  2. {  
  3.     ...  
  4.     //Invalidates the specified Drawable.   
  5.     //默认实现,重新绘制该视图本身   
  6.     public void invalidateDrawable(Drawable drawable) {  
  7.         if (verifyDrawable(drawable)) { //是否是同一个Drawable对象,通常为真   
  8.             final Rect dirty = drawable.getBounds();  
  9.             final int scrollX = mScrollX;  
  10.             final int scrollY = mScrollY;  
  11.             //重新请求绘制该View,即重新调用该View的draw()方法  ...   
  12.             invalidate(dirty.left + scrollX, dirty.top + scrollY,  
  13.                     dirty.right + scrollX, dirty.bottom + scrollY);  
  14.         }  
  15.     }  
  16.     ...  
  17. }  
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource 
{
	...
	//Invalidates the specified Drawable.
    //默认实现,重新绘制该视图本身
    public void invalidateDrawable(Drawable drawable) {
        if (verifyDrawable(drawable)) { //是否是同一个Drawable对象,通常为真
            final Rect dirty = drawable.getBounds();
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            //重新请求绘制该View,即重新调用该View的draw()方法  ...
            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()的流程,同时重点讲解下绘制背景的操作。


  1. //方法所在路径:frameworks\base\core\java\android\view\View.java   
  2. //draw()绘制过程   
  3. private void draw(Canvas canvas){    
  4. //该方法会做如下事情     
  5.   //1 、绘制该View的背景     
  6.     //其中背景图片绘制过程如下:   
  7.     //是否透明, 视图通常是透明的 , 为true   
  8.      if (!dirtyOpaque) {  
  9.        //开始绘制视图的背景   
  10.        final Drawable background = mBGDrawable;  
  11.        if (background != null) {  
  12.            final int scrollX = mScrollX;  //获取偏移值   
  13.            final int scrollY = mScrollY;  
  14.            //视图的布局坐标是否发生了改变, 即是否重新layout了。   
  15.            if (mBackgroundSizeChanged) {  
  16.              //如果是,我们的Drawable对象需要重新设置大小了,即填充该View。   
  17.                background.setBounds(00,  mRight - mLeft, mBottom - mTop);  
  18.                mBackgroundSizeChanged = false;  
  19.            }  
  20.            //View没有发生偏移   
  21.            if ((scrollX | scrollY) == 0) {  
  22.                background.draw(canvas); //OK, 该方法会绘制当前StateListDrawable的当前背景Drawable   
  23.            } else {  
  24.              //View发生偏移,由于背景图片值显示在布局坐标中,即背景图片不会发生偏移,只有视图内容onDraw()会发生偏移   
  25.              //我们调整canvas对象的绘制区域,绘制完成后对canvas对象属性调整回来   
  26.                canvas.translate(scrollX, scrollY);  
  27.                background.draw(canvas); //OK, 该方法会绘制当前StateListDrawable的当前背景Drawable   
  28.                canvas.translate(-scrollX, -scrollY);  
  29.            }  
  30.        }  
  31.    }  
  32.     ...  
  33.  //2、为绘制渐变框做一些准备操作     
  34.  //3、调用onDraw()方法绘制视图本身     
  35.  //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。     
  36.  //5、绘制渐变框       
  37. }    
//方法所在路径:frameworks\base\core\java\android\view\View.java
//draw()绘制过程
private void draw(Canvas canvas){  
//该方法会做如下事情  
  //1 、绘制该View的背景  
    //其中背景图片绘制过程如下:
	//是否透明, 视图通常是透明的 , 为true
	 if (!dirtyOpaque) {
	   //开始绘制视图的背景
       final Drawable background = mBGDrawable;
       if (background != null) {
           final int scrollX = mScrollX;  //获取偏移值
           final int scrollY = mScrollY;
           //视图的布局坐标是否发生了改变, 即是否重新layout了。
           if (mBackgroundSizeChanged) {
          	 //如果是,我们的Drawable对象需要重新设置大小了,即填充该View。
               background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
               mBackgroundSizeChanged = false;
           }
           //View没有发生偏移
           if ((scrollX | scrollY) == 0) {
               background.draw(canvas); //OK, 该方法会绘制当前StateListDrawable的当前背景Drawable
           } else {
          	 //View发生偏移,由于背景图片值显示在布局坐标中,即背景图片不会发生偏移,只有视图内容onDraw()会发生偏移
          	 //我们调整canvas对象的绘制区域,绘制完成后对canvas对象属性调整回来
               canvas.translate(scrollX, scrollY);
               background.draw(canvas); //OK, 该方法会绘制当前StateListDrawable的当前背景Drawable
               canvas.translate(-scrollX, -scrollY);
           }
       }
   }
	...
 //2、为绘制渐变框做一些准备操作  
 //3、调用onDraw()方法绘制视图本身  
 //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。  
 //5、绘制渐变框    
}  


      That's all ! 我们用到的知识点也就这么多吧。 如果大家有丝丝不明白的话,可以去看下源代码,具体去分析下这些流程到底

  是怎么走下来的。

      我们从宏观的角度分析了View绘制不同状态背景的原理,View框架就是这么做的。为了易于理解性,

  下面我们通过一个小Demo来演示前面种种流程。

   

 Demo 说明:


          我们参照View框架中绘制不同背景图的实现原理,自定义一个View类,通过给它设定StateListDrawable对象,使其能够在

   不同状态时能动态"绘制"背景图片。 基本流程方法和View.java类实现过程一模一样。

    截图如下:


                      


                 初始背景图                                                            触摸后显示的背景图(pressed)


  一、主文件MainActivity.java如下:

  1. /** 
  2.  *  
  3.  * @author http://http://blog.youkuaiyun.com/qinjuning 
  4.  */  
  5. public class MainActivity extends Activity  
  6. {  
  7.   
  8.     @Override  
  9.     public void onCreate(Bundle savedInstanceState)  
  10.     {  
  11.         super.onCreate(savedInstanceState);      
  12.   
  13.         LinearLayout ll  =  new LinearLayout(MainActivity.this);  
  14.         CustomView customView = new CustomView(MainActivity.this);   
  15.         //简单设置为 width 200px - height 100px吧    
  16.         ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(200 , 100);  
  17.         customView.setLayoutParams(lp);  
  18.         //需要将该View设置为可点击/触摸状态,否则触摸该View没有效果。   
  19.         customView.setClickable(true);  
  20.           
  21.         ll.addView(customView);  
  22.         setContentView(ll);   
  23.     }  
  24. }  
/**
 * 
 * @author http://http://blog.youkuaiyun.com/qinjuning
 */
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); 
        //简单设置为 width 200px - height 100px吧 
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(200 , 100);
        customView.setLayoutParams(lp);
        //需要将该View设置为可点击/触摸状态,否则触摸该View没有效果。
        customView.setClickable(true);
        
        ll.addView(customView);
        setContentView(ll); 
    }
}

   功能很简单,为Activity设置了视图 。


二、 自定义View如下 , CustomView.java :

  1. /**  
  2.  * @author http://http://blog.youkuaiyun.com/qinjuning 
  3.  */  
  4. //自定义View   
  5. public class CustomView extends View   /*extends Button*/  
  6. {  
  7.     private static String TAG = "TackTextView";  
  8.       
  9.     private Context mContext = null;  
  10.     private Drawable mBackground = null;  
  11.     private boolean mBGSizeChanged = true;;   //视图View布局(layout)大小是否发生变化   
  12.       
  13.     public CustomView(Context context)  
  14.     {  
  15.         super(context);  
  16.         mContext = context;         
  17.         initStateListDrawable(); // 初始化图片资源   
  18.     }  
  19.   
  20.     // 初始化图片资源   
  21.     private void initStateListDrawable()  
  22.     {  
  23.         //有两种方式获取我们的StateListDrawable对象:   
  24.         // 获取方式一、手动构建一个StateListDrawable对象   
  25.         StateListDrawable statelistDrawable = new StateListDrawable();  
  26.           
  27.         int pressed = android.R.attr.state_pressed;  
  28.         int windowfocused = android.R.attr.state_window_focused;  
  29.         int enabled = android.R.attr.state_enabled;  
  30.         int stateFoucesd = android.R.attr.state_focused;  
  31.         //匹配状态时,是一种优先包含的关系。   
  32.         // "-"号表示该状态值为false .即不匹配   
  33.         statelistDrawable.addState(new int[] { pressed, windowfocused },   
  34.                 mContext.getResources().getDrawable(R.drawable.btn_power_on_pressed));  
  35.         statelistDrawable.addState(new int[]{ -pressed, windowfocused },   
  36.                 mContext.getResources().getDrawable(R.drawable.btn_power_on_nor));      
  37.                  
  38.         mBackground = statelistDrawable;  
  39.           
  40.         //必须设置回调,当改变状态时,会回掉该View进行invalidate()刷新操作.   
  41.         mBackground.setCallback(this);         
  42.         //取消默认的背景图片,因为我们设置了自己的背景图片了,否则可能造成背景图片重叠。   
  43.         this.setBackgroundDrawable(null);  
  44.           
  45.         // 获取方式二、、使用XML获取StateListDrawable对象   
  46.         // mBackground = mContext.getResources().getDrawable(R.drawable.tv_background);   
  47.     }  
  48.       
  49.     protected void drawableStateChanged()  
  50.     {  
  51.         Log.i(TAG, "drawableStateChanged");  
  52.         Drawable d = mBackground;  
  53.         if (d != null && d.isStateful())  
  54.         {  
  55.             d.setState(getDrawableState());  
  56.             Log.i(TAG, "drawableStateChanged  and is 111");  
  57.         }  
  58.   
  59.        Log.i(TAG, "drawableStateChanged  and is 222");  
  60.        super.drawableStateChanged();  
  61.     }  
  62.     //验证图片是否相等 , 在invalidateDrawable()会调用此方法,我们需要重写该方法。   
  63.     protected boolean verifyDrawable(Drawable who)  
  64.     {  
  65.         return who == mBackground || super.verifyDrawable(who);  
  66.     }  
  67.     //draw()过程,绘制背景图片...   
  68.     public void draw(Canvas canvas)  
  69.     {  
  70.         Log.i(TAG, " draw -----");  
  71.         if (mBackground != null)  
  72.         {  
  73.             if(mBGSizeChanged)  
  74.             {  
  75.                 //设置边界范围   
  76.                 mBackground.setBounds(00, getRight() - getLeft(), getBottom() - getTop());  
  77.                 mBGSizeChanged = false ;  
  78.             }  
  79.             if ((getScrollX() | getScrollY()) == 0)  //是否偏移   
  80.             {  
  81.                 mBackground.draw(canvas); //绘制当前状态对应的图片   
  82.             }  
  83.             else  
  84.             {  
  85.                 canvas.translate(getScrollX(), getScrollY());  
  86.                 mBackground.draw(canvas); //绘制当前状态对应的图片   
  87.                 canvas.translate(-getScrollX(), -getScrollY());  
  88.             }  
  89.         }  
  90.         super.draw(canvas);  
  91.     }  
  92.     public void onDraw(Canvas canvas) {      
  93.         ...  
  94.     }  
  95. }  
/** 
 * @author http://http://blog.youkuaiyun.com/qinjuning
 */
//自定义View
public class CustomView extends View   /*extends Button*/
{
    private static String TAG = "TackTextView";
    
    private Context mContext = null;
    private Drawable mBackground = null;
    private boolean mBGSizeChanged = true;;   //视图View布局(layout)大小是否发生变化
    
    public CustomView(Context context)
    {
        super(context);
        mContext = context;       
        initStateListDrawable(); // 初始化图片资源
    }

    // 初始化图片资源
    private void initStateListDrawable()
    {
        //有两种方式获取我们的StateListDrawable对象:
        // 获取方式一、手动构建一个StateListDrawable对象
        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;
        //匹配状态时,是一种优先包含的关系。
        // "-"号表示该状态值为false .即不匹配
        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;
        
        //必须设置回调,当改变状态时,会回掉该View进行invalidate()刷新操作.
        mBackground.setCallback(this);       
        //取消默认的背景图片,因为我们设置了自己的背景图片了,否则可能造成背景图片重叠。
        this.setBackgroundDrawable(null);
        
        // 获取方式二、、使用XML获取StateListDrawable对象
        // mBackground = mContext.getResources().getDrawable(R.drawable.tv_background);
    }
    
    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();
    }
    //验证图片是否相等 , 在invalidateDrawable()会调用此方法,我们需要重写该方法。
    protected boolean verifyDrawable(Drawable who)
    {
        return who == mBackground || super.verifyDrawable(who);
    }
    //draw()过程,绘制背景图片...
    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,形式如下:

  1. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   <item android:state_pressed="true"   
  3.         android:state_window_focused="true"   
  4.         android:drawable="@drawable/btn_power_on_pressed"></item>  
  5.   <item android:state_pressed="false"   
  6.         android:state_window_focused="true"    
  7.         android:drawable="@drawable/btn_power_on_nor"></item>      
  8.         
  9. </selector>  
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_pressed="true" 
        android:state_window_focused="true" 
        android:drawable="@drawable/btn_power_on_pressed"></item>
  <item android:state_pressed="false" 
        android:state_window_focused="true"  
        android:drawable="@drawable/btn_power_on_nor"></item>    
      
</selector>

          基本上所有功能都在这儿显示出来了, 和我们前面说的一模一样吧。

          当然了,如果你想偷懒,大可用系统定义好的一套工具 , 即直接使用setBackgroundXXX()或者在设置对应的属性,但是,

     万变不离其宗,掌握了绘制原理,可以潇洒走江湖了。

     

源码地址: https://pan.quark.cn/s/a4b39357ea24 欧姆龙触摸屏编程软件MPTST 5.02是专门为欧姆龙品牌的工业触摸屏而研发的编程解决方案,它赋予用户在直观界面上构建、修改以及排错触摸屏应用程序的能力。 该软件在工业自动化领域具有不可替代的地位,特别是在生产线监视、设备操控以及人机互动系统中发挥着核心作用。 欧姆龙MPTST(Machine Process Terminal Software Touch)5.02版本配备了多样化的功能,旨在应对不同种类的触摸屏项目要求。 以下列举了若干核心特性:1. **图形化编程**:MPTST 5.02采用图形化的编程模式,允许用户借助拖拽动作来设计屏幕布局,设定按钮、滑块、指示灯等组件,显著简化了编程流程,并提升了工作效率。 2. **兼容性**:该软件能够适配欧姆龙的多个触摸屏产品线,包括CX-One、NS系列、NJ/NX系列等,使用户可以在同一个平台上完成对不同硬件的编程任务。 3. **数据通信**:MPTST 5.02具备与PLC(可编程逻辑控制器)进行数据交互的能力,通过将触摸屏作为操作界面,实现生产数据的显示与输入,以及设备状态的监控。 4. **报警与事件管理**:软件中集成了报警和事件管理机制,可以设定多种报警标准,一旦达到预设条件,触摸屏便会展示对应的报警提示,助力操作人员迅速做出响应。 5. **模拟测试**:在设备实际连接之前,MPTST 5.02支持用户进行脱机模拟测试,以此验证程序的正确性与稳定性。 6. **项目备份与恢复**:为了防止数据遗失,MPTST 5.02提供了项目文件的备份及还原功能,对于多版本控制与团队协作具有显著价值。 7. **多语言支持**:针对全球化的应...
本资源包为流体力学与化学传质交叉领域的研究提供了一套完整的数值模拟解决方案,重点针对湍流条件下通道内溶解物质的输运与分布规律进行定量分析。该工具集专为高等院校理工科专业的教育与科研需求设计,尤其适合计算机科学、电子工程及数学等相关学科的本科生在完成课程项目、综合设计或学位论文时使用。 软件环境兼容多个版本的MatLAB平台,包括2014a、2019b及后续的2024b发行版,确保了在不同实验室或个人计算环境中的可移植性。资源包内预置了经过验证的示例数据集,用户可直接调用主程序执行计算,显著降低了初始学习成本,使初学者能够迅速掌握基本操作流程。 代码架构采用模块化与参数驱动设计。所有关键物理参数(如流速、扩散系数、边界条件等)均集中于独立的配置模块,用户无需深入底层算法即可灵活调整计算条件,从而高效模拟多种湍流溶解场景。程序逻辑结构清晰,各功能段均配有详尽的说明注释,既阐述了数值方法的理论依据,也解释了关键步骤的实现意图,便于使用者理解模型构建过程并进行针对性修改。 在学术训练方面,本工具能够帮助学生将抽象的流体动力学与传质理论转化为可视化的数值实验结果,深化对湍流混合、浓度边界层等概念的理解。对于毕业设计或专题研究,其参数化框架支持用户嵌入自定义模型,开展创新性数值实验,为深入研究复杂流动中的溶解机制提供可靠的技术支撑。 总体而言,该MATLAB分析工具集通过结构化的代码设计、完备的案例支持与广泛的版本兼容性,为流体溶解现象的数值研究提供了一个高效、可扩展的计算平台,兼具教学示范与科研探索的双重价值。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
标题JSPM自行车个性化改装推荐系统研究AI更换标题第1章引言介绍自行车个性化改装推荐系统的研究背景、意义及国内外研究现状。1.1研究背景与意义阐述自行车个性化改装需求增长及推荐系统的重要性。1.2国内外研究现状分析国内外自行车改装推荐系统的研究进展及不足。1.3研究方法及创新点概述JSPM系统的设计方法及相较于其他系统的创新点。第2章相关理论介绍与自行车个性化改装推荐系统相关的理论基础。2.1个性化推荐理论阐述个性化推荐的基本原理和常用算法。2.2自行车改装知识介绍自行车结构、部件及改装选项等基础知识。2.3用户偏好分析理论讨论如何分析用户偏好以实现精准推荐。第3章JSPM系统设计详细介绍JSPM自行车个性化改装推荐系统的设计方案。3.1系统架构设计阐述系统的整体架构、模块划分及功能。3.2数据库设计介绍系统数据库的设计思路、表结构及关系。3.3推荐算法设计详细介绍基于用户偏好的推荐算法实现过程。第4章系统实现与测试介绍JSPM系统的实现过程及测试方法。4.1系统开发环境与工具说明系统开发所使用的环境、工具及技术栈。4.2系统实现过程阐述系统从设计到实现的具体步骤和关键代码。4.3系统测试与优化介绍系统的测试方法、测试结果及优化措施。第5章研究结果与分析展示JSPM系统的实验分析结果并进行讨论。5.1实验数据与指标介绍实验所采用的数据集、评估指标及实验环境。5.2实验结果展示通过图表等形式展示实验结果,包括推荐准确率等。5.3结果分析与讨论对实验结果进行详细分析,讨论系统的优缺点及改进方向。第6章结论与展望总结JSPM自行车个性化改装推荐系统的研究成果并展望未来。6.1研究结论概括本文的主要研究成果,包括系统设计、实现及实验结果。6.2展望指出系统存在的不足,提出未来研究的方向和改进措施。
下载前必看:https://pan.quark.cn/s/a4b39357ea24 在计算机科学范畴内,编译原理被视为一个关键分支,它主要探讨编程语言如何转化为机器可识别的形式。 本次实验的核心内容是关于非确定有限自动机(NFA)的确定化与最小化这两个重要过程,这两个概念对于深入理解正则表达式以及编译器设计具有决定性意义。 我们将详细研究NFA,并分析其确定化、最小化的相关理论及在C++中的具体实现。 非确定有限自动机(Non-Deterministic Finite Automaton,简称NFA)是一种用于识别正则语言的计算模型。 与确定有限自动机(DFA)相比较,NFA在处理输入信号时能够存在多个状态转移路径。 在NFA的结构中,一个状态对于相同的字符输入可以产生多个不同的输出,这一特性使得NFA在某些应用场景下更为强大,能够识别更为复杂的正则表达式。 将NFA进行确定化,指的是将其转化为一个等价的DFA。 在确定的DFA中,当处理输入信号时,只有一个明确的路径可供选择,这种结构对于实际操作来说更为高效。 确定化NFA的过程通常涉及到ε-闭包(epsilon closure)的概念,即针对一个状态集合,找出所有可以通过ε(空字符)从该集合中的任一状态移动到达的状态集合。 通过逐步构建状态转移表,我们可以逐步形成等价的DFA。 NFA的最小化过程是指找到一个与原始NFA等价但状态数量最少的DFA。 最小化DFA的主要目的在于减少内存的使用量并提升执行效率。 常用的最小化方法是基于子集构造法,通过对比每个状态集合的特性(例如是否接受或拒绝特定的输入信号),逐步合并相似的状态集合,直到无法继续合并为止。 C++作为一种功能强大的系统级编程语言,提供了丰富的数据结构与算法支持,非常适合用...
内容概要:本文介绍了一个基于ESP32和BLE Mesh技术的“无网区”应急救援定位系统,适用于断网、断电等极端环境下的人员定位与生命体征监测。系统利用ESP32实现自组网通信,通过BLE Mesh网络泛洪优化、RSSI三角测量与粒子滤波算法实现0.8米精确定位,并集成ESP-NOW双模冗余传输保障数据可靠。系统支持WebBluetooth直连查看、Three.js构建3D可视化大屏及VR沉浸式展示,具备低功耗特性(平均800µA),配合太阳能可长期运行。代码层面展示了可穿戴节点与汇聚网关的核心实现,涵盖数据封装、多通道传输、JSON上云与WebSocket实时推送,满足计算机竞赛中高鲁棒性、高可视化的评审要求。; 适合人群:具备嵌入式开发基础、物联网或计算机相关专业、参与大学生计算机类竞赛(如物联网赛道、红旅赛道)的学生团队及指导教师;有一定C++/Arduino编程经验的研发人员。; 使用场景及目标:①应用于地震废墟、雪崩救援、偏远地区走失定位等无公网场景的应急系统设计;②作为全国大学生计算机竞赛中的高分项目模板,突出技术创新、稳定性与现场表现力;③学习BLE Mesh自组网、低功耗设计、多模通信冗余、三维可视化等关键技术实现。; 阅读建议:此资源紧密结合竞赛实际需求,建议读者深入理解代码结构与通信机制,重点掌握BLE Mesh与ESP-NOW协同策略、定位算法优化及数据可视化链路搭建,并结合硬件实测进行调试与性能验证,全面提升项目落地能力与答辩竞争力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值