Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android自定義View全解

android自定義View全解

編輯:關於Android編程

本文主要有以下內容:

* 自定義View的分類
* 自定義View的注意事項
* 自定義View的實現
* 自定義View使其支持wrap_content和padding
* 自定義屬性的實現過程

首先,自定義View是為了達到更絢麗的效果。它相對來說也是一個比較難的技術體系,涉及到【View的層次結構】、【View事件分發機制】、【View的工作原理】、【View的彈性滑動】、【View的滑動沖突】等技術。

自定義View的分類

1.1. 繼承 View重寫 onDraw 方法

這種方式主要用於顯示不規則的效果哦,即這種效果不方便用布局組合來實現,往往需要靜態或者動態的顯示一些不規則的圖形
采用這種方式需要自己支持wrap_content,並且padding也需要自己處理。

1.2. 繼承ViewGroup派生特殊的Layout

這種方法主要用於實現自定義布局,即除了LinearLayout,RelativeLayout等系統布局之外的一種重新定義的全新的布局,當某種效果很像
幾種View組合在一起的時候就可以采用這種方法。
這種方法稍微復雜一些,需要合適的處理ViewGroup的測量和布局這倆個過程

1.3. 繼承特定的View(比如TextView)

這種方法一般用於擴展某種已有的View功能。這種方法不需要自己支持wrap_content,padding等。

1.4. 繼承特定的ViewGroup(比如LinearLayout等)

當某種效果很像幾種View組合在一起的時候就可以采用這種方法。這種方法不需要自己處理ViewGroup的測量和布局這倆個過程。
與1.2比較,一般來說,1.2能實現的效果1.4也都能實現,但1.2更接近View的底層。

自定義View的注意事項

2.1. 讓View支持wrap_content

這是因為直接繼承View或ViewGroup的控件,如果不在onMeasure中處理wrap_content,那麼外界在布局中使用wrap_content時就無法達到預期效果

2.2. 讓View支持padding

直接繼承View的控件,如果不再draw方法中處理padding,那麼這個屬性是無法起作用的。
直接繼承ViewGroup的控件需要在onMeasure和onLayout中考慮padding和子元素的margin對其造成的影響,不然將導致pading和子元素的margin失效

2.3. 不要在View中使用Handler

這是因為View內部本身就提供了post系列方法,完全可以替代Handler的作用。除非你很明確要用Handler來發送消息。

2.4. View中如果有線程和動畫,及時停止

如果有線程和動畫需要停止的時候,onDetachedFromWindow就惡意做到。這是因為當包含此View的Activity退出或者當前View被remove時,View的onDetachedFromWindow方法就會被調用。相對的,當包含此View的Activity啟動時onAttachedToWindow會被調用。同時,View不可見時,我們也需要停止線程和動畫,如果不及時停止,可能會導致內存洩漏。

2.5. 如果有滑動嵌套時,當然要處理好滑動沖突的問題。

================================================

繼承View重寫 onDraw 方法

3.1 代碼
public class CircleView extends View {
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    public CircleView(Context context) {
        this(context, null);
    }
    public CircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
       super(context, attrs, defStyleAttr);
        mPaint.setColor(mColor);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(width / 2, height / 2, radius, mPaint);
    }
}


    

上述代碼就完成了基本的自定義View,同樣上述1.1,2.2,2.2中提到過,我們需要自己實現使其支持wrap_content和padding,現在我們就用上述代碼來驗證當前自定義view是否支持wrap_content和padding

3.2 代碼驗證當前自定義View是否支持wrap_content和padding

QQ截圖20160621120732.png

QQ截圖20160621120956.png

圖一:證明支持layout_margin屬性
圖二:證明不支持padding屬性
圖三、圖五:證明不支持wrap_content

layout_margin屬性是由父容器控制的,所以支持,不需要我們處理<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoNiBpZD0="33讓view支持wrapcontent"> 3.3讓View支持wrap_content

之所以wrap_content失效,是因為:如果View在使用wrap_content時,那麼他的specMode是AT_MOST模式,這種模式下它的寬高等於specSize,而這種情況下specSize是parentSize,而parentSize是父容器當前剩余的大小。
解決方法就是:我們為View指定一個默認的寬高,並在wrap_content時使用此寬高即可。當然這個默認值如何取捨,沒有一個固定的依據,我們只能根據需求自行選取一個合適的值。下面的代碼是通過Math.min函數來設置默認值的,道理一樣。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int mWidth=600;
        int mHeight =600;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if(widthMode==MeasureSpec.AT_MOST && heightMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(Math.min(mWidth, widthSize),Math.min(mHeight, heightSize));
        }else if(widthMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(Math.min(mWidth, widthSize),heightSize);
        }else if( heightMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSize,Math.min(mWidth, widthSize));
        }
    }

測試修改後的View:

        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

QQ截圖20160621135301.png

3.4讓View支持padding屬性。

這個只需要在繪制的時候把padding考慮進去即可。
首先在onDraw方法中通過

int width = getWidth() - (paddingLeft + paddingRight);
int height = getHeight() - (paddingTop + paddingBottom);

重新計算圓的實際寬高,
然後,通過

canvas.drawCircle(paddingLeft+width / 2, paddingTop+height / 2, radius, mPaint);

重新定義圓的圓點,使其四周留下空白(也就是padding)
全部代碼如下:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - (paddingLeft + paddingRight);
        int height = getHeight() - (paddingTop + paddingBottom);
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(paddingLeft+width / 2, paddingTop+height / 2, radius, mPaint);
    }

    

繼承特定的ViewGroup(比如LinearLayout等)

這種方式主要用於實現自定義布局,實現起來也比較簡單,直接上代碼:

ActionBarView.java:
public class ActionBarView extends LinearLayout {
    public static final int KEY_BACK = 111;
    public static final int KEY_CONTENT = 222;
    private Context context = null;
    private ImageView iv_back;
    private TextView tv_context;
    private OnClickListener onClickListener = null;
    public ActionBarView(Context context) {
        this(context, null);
    }
    public ActionBarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        View contentView = LayoutInflater.from(context).inflate(R.layout.actionbar_layout, this);
        iv_back = (ImageView) contentView.findViewById(R.id.iv_back);
        tv_context = (TextView) contentView.findViewById(R.id.tv_context);
        iv_back.setOnClickListener(new MyOnClickListener(KEY_BACK));
        tv_context.setOnClickListener(new MyOnClickListener(KEY_CONTENT));
    }
    // 通過這個方法來動態設置標題
    public void setContent(String str) {
        tv_context.setText(str);
    }
    // 通過回調接口在外部監聽點擊事件
    private class MyOnClickListener implements android.view.View.OnClickListener {
        private int key = -1;
        private MyOnClickListener(int position) {
            this.key = position;
        }
        @Override
        public void onClick(View v) {
            if (onClickListener != null) {
                onClickListener.onClick(key, v);
            }
        }
    }
    public void setOnClickListener(OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }
    public interface OnClickListener {
        public void onClick(int key, View v);
    }
}

actionbar_layout.xml:

<framelayout android:background="@drawable/bg_top" android:layout_height="44dip" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
    
    
</framelayout>

activity的xml文件中引用:
    

activity中:
        ActionBarView barView = (ActionBarView) findViewById(R.id.barView);
        barView.setContent("自定義View標題");
        barView.setOnClickListener(new ActionBarView.OnClickListener() {
            @Override
            public void onClick(int key, View v) {
                if (key == ActionBarView.KEY_BACK) {
                    Log.i("CustomViewMainActivity", "ActionBarView 點擊返回按鈕");
                }
                if (key == ActionBarView.KEY_CONTENT) {
                    Log.i("CustomViewMainActivity", "ActionBarView 點擊標題");
                }
            }
        });

QQ截圖20160621144146.png

圖中,藍色區域是整個ActionBarView 所占的區域,藍色區域外是的空白是父布局設置的padding,藍色區域和其圍著的白色區域之間的距離是ActionBarView 設置的padding。所以,這種方式會自動支持padding屬性,不需要我們自己處理。

代碼中通過public void setContent(String str)方法來動態設置標題,其實我們還可以在xml中來設置,不過這需要我們自定義屬性,這個稍後來說。

繼承ViewGroup派生特殊的Layout

這種方式使用起來比較復雜,而且也不是很常用,這裡就先不說了。

繼承特定的View

這裡我們繼承EditText,實現全部刪除的功能,【源碼見附件】

自定義屬性

有時候,業務需要,或者是單純的為了使用方便,我們需要自定義屬性,使其可以在xml中快速配置屬性。比如CircleView中我們可以配置背景色的屬性,ActionBarView我們可以配置標題屬性。
這裡我們以CircleView為例(ActionBarView的自定義屬性見【源碼】,這裡就不往出貼了):
第一步:在res/values/下創建atts開頭的自定義屬性xml,內容如下:



    
        
    

上述代碼首先定義了一個名稱為CircleView的屬性集合,當然,既然是集合,就可以有多個屬性。這裡的屬性名是circle_color,格式是color
第二步:在View的構造方法中解析自定義屬性的值並做處理,代碼如下:

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
//        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleView, defStyleAttr, 0);
//        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView, defStyleAttr, 0);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.YELLOW);
        a.recycle();
        mPaint.setColor(mColor);
    }

**第三步:在xml布局中使用自定義屬性,代碼如下:



    

這裡需要注意:必須在布局文件中添加schemas聲明:


xmlns:app="http://schemas.android.com/apk/res-auto"。
其中,app是自定義屬性的前綴,與下邊的app:circle_color="@color/colorAccent"呼應。當然這個前綴是可以改的,是可以自定義的。

另外自定義屬性有如下格式:

reference:指資源ID color:顏色值。 boolean:布爾值 dimension:尺寸值。 float:浮點值。 integer:整型值 string:字符串。 fraction:百分數 enum:枚舉值
flag:位或運算 多類型。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved