Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> UI--Android中的狀態切換按鈕自定義

UI--Android中的狀態切換按鈕自定義

編輯:關於Android編程

 

1.概述

  Android中關於控制開關和頁面/狀態切換的使用場景還是比較多的。源生做的支持也有比如RadioGroup 和Tabhost等。這裡准備通過自定義View來模仿學習下IOS兩種常見UI樣式: SwitchButtonSegmentControl
  首先先通過簡易的組裝View來實現兩種UI的相應效果,其次呢,嘗試通過繪制來達到同樣的更靈活的樣式。代碼前後共實現按鈕切換和頁面切換兩個樣式,三種實現方案,其中,兩種SwitchButton實現,一種SegmentControl實現。實現方案中關於自定義View繪制,本篇只講述SwitchView,希望大家能舉一反三,同樣做到SegmentControl的相同效果。個人也更傾向於使用自定義實現,更方便靈活。
  先看效果圖:
  示例
  頭部即為切換頁面的SegmentControl,然後第一行是通過組裝view來實現SwitchButton,第二行則是完全繪制出來的SwitchButton效果。接下來我們分別一一講述代碼實現。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxociAvPg0KPGgyIGlkPQ=="2switchbutton樣式兩種實現">2.SwitchButton樣式兩種實現

  狀態開關按鈕常用於某些控制開關,設置選項裡最為常見。

2.1 組合View實現

  該方法比較簡單明了,定義三個view,開啟狀態和關閉狀態兩個背景View,一個圓形按鈕view。點擊時候利用滑動動畫移動按鈕和狀態背景,達到類似的視覺效果。
  先看xml布局:




    <framelayout android:layout_height="wrap_content" android:layout_width="wrap_content">

        

        
    </framelayout>

    

  因為是幀布局,所以頂層使用merge(merge簡化xml不解釋,自行百度)。然後使用兩個開關狀態背景和一個圓形按鈕組合而成。

1. 全局變量參數

public class SwitchView extends FrameLayout {
    protected boolean isChecked;  //是否選中狀態
    protected View onBgView;
    protected View offBgView;
    protected View circleView;
    protected boolean autoForPerformClick = true; //是否允許點擊自動切換
    protected OnCheckedChangedListener onCheckedChangedListener; //切換事件監聽

    //...
}

  一般狀態切換是由click事件監聽,根據業務邏輯來判斷是否切換狀態。但對於switchButton,通常我們操作時直觀感受應該是先切換了狀態才執行相應操作的,所以我們在performClick事件中直接根據autoForPerformClick 的狀態來相應點擊操作。

至於performClick ,其實就是控制條用onClickListener的方法體,具體邏輯在View源碼中查看。

2. 初始化

    public SwitchView(Context context) {
        super(context);
        initialize();
    }

    public SwitchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public SwitchView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
    }

    protected void initialize() {
        setClickable(true);
        LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        layoutInflater.inflate(R.layout.switch_view, this);
        onBgView = findViewById(R.id.on_bg_view);
        offBgView = findViewById(R.id.off_bg_view);
        circleView = findViewById(R.id.circle_view);
    }

3. 點擊響應

    @Override
    public boolean performClick() { 
        if (!autoForPerformClick) //如果不是自動響應則調用默認處理方法
            return super.performClick();
        /**
        *否則直接切換switch狀態並觸發事件監聽
        */
        setChecked(!isChecked, true);
        if (onCheckedChangedListener != null) {
            onCheckedChangedListener.onChanged(this, isChecked);
        }
        return super.performClick();
    }

  View點擊後會執行performClick方法,並判斷是否調用clickLisentener。這裡我們直接重寫performClick方法,如果自動響應autoForPerformClick為ture則直接切換Switch狀態,否則調用默認處理邏輯。

4.切換狀態動畫

  點擊打開,則圓形按鈕從左端滑動到右端,onBg顯示,offBg隱藏;
  再點擊關閉,圓形按鈕從右端滑動到左端,onBg隱藏,offBg顯示。

public void setChecked(boolean value, boolean needAnimate) {
        if (isChecked == value)
            return;
        isChecked = value;

        float targetX = 0; //要移動的目標位置
        if (getWidth() != 0) {  //當前view沒有渲染上去時候,getWidth()為零
            targetX = getWidth() - circleView.getWidth();
        } else {
            measure(0, 0);
            targetX = getMeasuredWidth() - circleView.getMeasuredWidth();
        }

        long durationMillis = needAnimate ? 200 : 0;
        if (isChecked) {
            onBgView.bringToFront(); //顯示在最前端
            onBgView.setVisibility(View.VISIBLE);
            offBgView.setVisibility(View.VISIBLE);

            //平移動畫
            TranslateAnimation an1 = new TranslateAnimation(0, targetX, 0, 0);
            an1.setFillAfter(true);
            an1.setDuration(durationMillis);
            circleView.startAnimation(an1);

            //透明度動畫
            AlphaAnimation an2 = new AlphaAnimation(0, 1);
            an2.setFillAfter(true);
            an2.setDuration(durationMillis);
            onBgView.startAnimation(an2);
        } else {
            offBgView.bringToFront();
            onBgView.setVisibility(View.VISIBLE);
            offBgView.setVisibility(View.VISIBLE);

            TranslateAnimation an1 = new TranslateAnimation(targetX, 0, 0, 0);
            an1.setFillAfter(true);
            an1.setDuration(durationMillis);
            circleView.startAnimation(an1);

            AlphaAnimation an2 = new AlphaAnimation(0, 1);
            an2.setFillAfter(true);
            an2.setDuration(durationMillis);
            offBgView.startAnimation(an2);
        }
    }

  狀態切換的兩個參數,value是否打開狀態,needAnimate是否需要動畫(否則直接切換效果)。setFillAfter保留動畫結束狀態,但並不影響View本身位置和狀態。切換時,先將當前顯示背景移動到最前端,其次添加按鈕動畫和漸隱動畫。
  至此,最基本的組合View實現已經完成了。想要了解詳情的請在源碼中查看。源碼分為兩部分,一個項目是View的實現lib,另一塊是示例演示demo.
  

2.2 自定義View繪制實現

  由於該樣式並不十分復雜,所以可以通過基本的圖形繪制draw出同樣的效果。
  具體實現邏輯:通過自定view屬性來確定按鈕大小和中間圓鈕大小,在測量onMesure方法中控制測量值mode和Size,並在onLayout方法中得到圓鈕半徑和起始點位置。然後進行繪制,先繪制底部on圓角矩形背景,再繪制off漸變縮放的圓角矩形,最後繪制spot圓鈕。
  嘴比較笨拙,又不會畫圖。用word的圖形工具將就畫下可以看就好了。
  簡易示范圖
  具體實現大體都類似,這裡貼上主要部分代碼

1.全局參數 

public class SwitchButton extends View{
    /** */
    private float radius;
    /** 開啟顏色*/
    private int onColor = Color.parseColor(#4ebb7f);
    /** 關閉顏色*/
    private int offBorderColor = Color.parseColor(#dadbda);
    /** 灰色帶顏色*/
    private int offColor = Color.parseColor(#ffffff);
    /** 手柄顏色*/
    private int spotColor = Color.parseColor(#ffffff);
    /** 邊框顏色*/
    private int borderColor = offBorderColor;
    /** 畫筆*/
    private Paint paint ;
    /** 開關狀態*/
    private boolean toggleOn = false;
    /** 邊框大小*/
    private int borderWidth = 2;
    /** 垂直中心*/
    private float centerY;
    /** 按鈕的開始和結束位置*/
    private float startX, endX;
    /** 手柄X位置的最小和最大值*/
    private float spotMinX, spotMaxX;
    /**手柄大小 */
    private int spotSize ;
    /** 手柄X位置*/
    private float spotX;
    /** 關閉時內部灰色帶高度*/
    private float offLineWidth;
    /** */
    private RectF rect = new RectF();
    /** 默認使用動畫*/
    private boolean defaultAnimate = true;

    private OnSwitchChanged listener;

    //...
}

2.初始化與讀取 

  讀取自定義屬性並賦值。講了又講的東西,略。

3.測量onMeasure與布局onLayout

  在onMeasure方法中根據給定mode和size來限定View,如果高寬不為明確值(UNSPECIFIED/AT_MOST),則定義自身高寬為明確值。 關於MeasureSpec的詳細講解,這裡附上愛哥的一篇文章–MeasureSpec,深入到賦值讀取的內部,不妨試著深入研究下。當然,更直接的方法就是點開源碼一探究竟咯。
  onLayout方法中取得view的實際高寬,計算出圓角矩形半徑,圓鈕半徑以及起始點x方向位置。還有On矩形和off矩形的寬度。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        /**
        *如果高寬未指定,則使用內置高寬明確大小
        */
        Resources r = Resources.getSystem();
        if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
            widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics());
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        }

        if(heightMode == MeasureSpec.UNSPECIFIED || heightSize == MeasureSpec.AT_MOST){
            heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        final int width = getWidth();
        final int height = getHeight();

        /**
        *測量相應大小
        */
        radius = Math.min(width, height) * 0.5f;
        centerY = radius;
        startX = radius;
        endX = width - radius;
        spotMinX = startX + borderWidth;
        spotMaxX = endX - borderWidth;
        spotSize = height - 4 * borderWidth;
        spotX = toggleOn ? spotMaxX : spotMinX;
        offLineWidth = 0;
    }

  前三步完成基本賦值之後,開始設置和綁定相應事件。這裡不作為重點部分也省略,主要講一下繪制過程和核心控制邏輯。
    

4.繪制過程

  按照前面的簡易示例圖來繪制我們的ui圖。

@Override
    public void draw(Canvas canvas) {
        //繪制on背景
        rect.set(0, 0, getWidth(), getHeight());
        paint.setColor(borderColor);
        canvas.drawRoundRect(rect, radius, radius, paint);

        //繪制off背景(縮放至0時候不繪制)
        if(offLineWidth > 0){
            final float cy = offLineWidth * 0.5f;
            rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy);
            paint.setColor(offColor);
            canvas.drawRoundRect(rect, cy, cy, paint);
        }

        //繪制圓鈕輪廓border
        rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
        paint.setColor(borderColor);
        canvas.drawRoundRect(rect, radius, radius, paint);

        //繪制圓鈕
        final float spotR = spotSize * 0.5f;
        rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
        paint.setColor(spotColor);
        canvas.drawRoundRect(rect, spotR, spotR, paint);

    }

  及诶按來便是我們的狀態切換動畫控制邏輯,即點擊按鈕之後setToggleOn或者setToggleOff執行的相應動作。

4.狀態切換動畫效果

    /**
    * 執行效果,如果animate為true表示有動畫效果
    * 否則直接執行計算並顯示最終打開1或者關閉0的效果繪制
    */
    private void takeEffect(boolean animate) {
        if(animate){
            slide();
        }else{
            calculateEffect(toggleOn ? 1 : 0);
        }
    }

    /**
    *這裡偷個懶,直接使用空的animation,根據當前interpolatedTime(0~1)漸變過程來繪制不同階段的View,達到動畫效果
    *當然,也可以開啟個線程或者定時任務,來實現從0到1的變換,勁兒改變視圖繪制過程
    */
    private void slide(){
            Animation animation = new Animation() {
                @Override
                protected void applyTransformation(float interpolatedTime,
                        Transformation t) {
                    if(toggleOn){
                        calculateEffect(interpolatedTime);
                    }else{
                        calculateEffect(1-interpolatedTime);
                    }
                }
            };
            animation.setDuration(200);
            clearAnimation();
            startAnimation(animation);
    }

    /**
    *計算繪制位置
    *mapValueFromRangeToRange方法計算從當前位置相對於目標位置所對應的值
    *通過顏色變化來達到透明度動畫效果(顏色漸變)
    */
    private void calculateEffect(final double value) {
        final float mapToggleX = (float) mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
        spotX = mapToggleX;

        float mapOffLineWidth = (float) mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);

        offLineWidth = mapOffLineWidth;

        final int fb = Color.blue(onColor);
        final int fr = Color.red(onColor);
        final int fg = Color.green(onColor);

        final int tb = Color.blue(offBorderColor);
        final int tr = Color.red(offBorderColor);
        final int tg = Color.green(offBorderColor);

        int sb = (int) mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);
        int sr = (int) mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);
        int sg = (int) mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);

        sb = clamp(sb, 0, 255);
        sr = clamp(sr, 0, 255);
        sg = clamp(sg, 0, 255);

        borderColor = Color.rgb(sr, sg, sb);

        postInvalidate();
    }

  以上就是自定義View繪制的核心代碼,詳細查看源碼SwitchButton。相較於組合方法,它更便捷,也有更高的靈活性和擴展性。同時還不需要圖片資源支持。


3.SegmentControl樣式實現

  常見的Tab有很多種,這裡使用的是IOS常見的一種切換效果SegmentControl。本篇只用最簡單的拼裝View實現類似效果。有興趣的可以自己嘗試繪制達到更優效果。(有空的話也會在後邊放出)

通過view組合生成 最近單的方案,沒有之一。使用現成的selector和背景來控制顯示效果。各個子view分別繼承 RelativeLayout並實現OnClick接口。最後在Segment中控制顯示和點擊切換。 自定義View繪制生成 這裡只是提供思路。定義一個ItemView,根據在Segment中位置揮之不同效果。背景效果會用selector.xml的都知道,使用shape標簽產生的drawable對象,其實就是一個GradientDrawable。所以我們自定義view可以直接通過使用GradientDrawable的setCornerRadii(float[] radii) 來繪制同樣的背景效果,勁兒可以做到不同顏色。最後,使用一個ViewGroup不含這些item即可。通過click事件來切換tab就可以了。

3.1 組合View實現

  首先,類似的定義一個可點擊的通用的RelativLayout。(實現 Checkable接口使其可被選中也移除選中狀態,詳細可以參考前面的博文 微博/動態 點贊效果)。這裡涉及三個新內容,稍微說明講解下。
  
- checkMode 選中模式,是單選 CHECKMODE_CHECK 還是 CHECKMODE_RADIO 單選效果。使我們的自定義RelativeLayout可以做到單選和復選。
- onInitializeAccessibilityEvent 添加View接受事件源信息。即訂閱checked事件。由於事件可能由內部子view點擊觸發,所以這裡應該接收並處理相應的checked事件。當然,使用該方法首先要重寫onInitializeAccessibilityNodeInfo方法,添加我們關注的狀態信息。
- SavedState狀態保存 當我們內部可能嵌套復雜view的時候,為了防止數據狀態丟失,一般需要定義狀態保存類,用以保存和恢復當前View狀態。

#### 1.可點擊的通用RelativeLayout

繼承實現Clickable接口,簡要略過。

    //定義checked狀態
    public static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };

    //重寫SetChecked方法和isChecked方法略

    /**
    *根據當前選擇模式checkMode 來控制單復選
    */
    @Override
    public boolean performClick() {
        if (checkMode == CHECKMODE_CHECK) {
            toggle();
        } else if (checkMode == CHECKMODE_RADIO) {
            setChecked(true);
        }
        return super.performClick();
    }

    /**
    *添加Drawable 的checked狀態 ,並再繪制view是繪制相應狀態效果
    */
    @Override
    public int[] onCreateDrawableState(int extraSpace) {
        int[] states = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(states, CHECKED_STATE_SET);
        }
        return states;
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        Drawable drawable = getBackground();
        if (drawable != null) {
            int[] myDrawableState = getDrawableState();
            drawable.setState(myDrawableState);
            invalidate();
        }
    }
接受checked狀態事件信息
    @Override
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(CheckedRelativeLayout.class.getName());
        event.setChecked(checked);
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(CheckedRelativeLayout.class.getName());
        info.setCheckable(true);
        info.setChecked(checked);
    }
保存View狀態和恢復
  View自身重寫保存和恢復的方法
  @Override
    public Parcelable onSaveInstanceState() {//保存
        Parcelable superState = super.onSaveInstanceState();

        SavedState ss = new SavedState(superState);

        ss.checked = isChecked();
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {//恢復
        SavedState ss = (SavedState) state;

        super.onRestoreInstanceState(ss.getSuperState());
        setChecked(ss.checked);
        requestLayout();
    }

  用於保存數據的基本狀態類型

static class SavedState extends BaseSavedState {
        boolean checked;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            checked = (Boolean) in.readValue(null);
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeValue(checked);
        }

        public static final Creator CREATOR = new Creator() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

        @Override
        public String toString() {
            return CompoundButton.SavedState{ + Integer.toHexString(System.identityHashCode(this)) +  checked= + checked + };
        }

2.控制tab切換的SegmentView

  代碼比較易於理解,這裡直接貼出來查閱即可。
  基本思路,水平線性布局包裹對應左中右不同item個數的選項,並通過設置對應left/right/center來設置背景。然後分別為每個Item設置同一個點擊事件,點擊之後檢查是否當前item被選中,改變statu,同時出發切換事件。詳細代碼:

public class SegmentView extends LinearLayout {

    protected final static int SEGMENT_LEFT_BG = R.drawable.segment_left_selector;
    protected final static int SEGMENT_CENTER_BG = R.drawable.segment_center_selector;
    protected final static int SEGMENT_RIGHT_BG = R.drawable.segment_right_selector;

    protected int leftBg = SEGMENT_LEFT_BG;
    protected int centerBg = SEGMENT_CENTER_BG;
    protected int rightBg = SEGMENT_RIGHT_BG;

    protected CheckedRelativeLayout2[] checkedRelativeLayouts;
    protected int index = -1;
    protected float textSize = -1;
    protected int textColorN = Color.BLACK, textColorP = Color.BLACK;
    protected OnIndexChangedListener onIndexChangedListener;

    public SegmentView(Context context) {
        super(context);
        initialize();
    }

    public SegmentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
        initFromAttributes(context, attrs);
    }

    public SegmentView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
        initFromAttributes(context, attrs);
    }

    protected void initialize() {
        setGravity(Gravity.CENTER);
    }

    protected void initFromAttributes(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SegmentView);
        String content = a.getString(R.styleable.SegmentView_content);
        index = a.getInt(R.styleable.SegmentView_index, index);
        textSize = a.getDimension(R.styleable.SegmentView_textSize, textSize);
        textColorN = a.getColor(R.styleable.SegmentView_textColorN, textColorN);
        textColorP = a.getColor(R.styleable.SegmentView_textColorP, textColorP);
        leftBg = a.getResourceId(R.styleable.SegmentView_leftBg, leftBg);
        centerBg = a.getResourceId(R.styleable.SegmentView_centerBg, centerBg);
        rightBg = a.getResourceId(R.styleable.SegmentView_rightBg, rightBg);
        a.recycle();

        if (!TextUtils.isEmpty(content)) {
            String[] contentStrings = content.split(,);
            setContent(contentStrings);
        }
        setIndex(index);
    }

    public void setContent(String... content) {
        View[] views = new View[content.length];
        for (int i = 0, len = content.length; i < len; i++) {
            String s = content[i];
            TextView tv = new TextView(getContext());
            tv.setTextColor(textColorN);
            tv.setText(s);
            if (textSize != -1) {
                tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            }
            views[i] = tv;
        }
        setContent(views);
    }

    public void setContent(View... content) {
        removeAllViews();
        int lastIndex = content.length - 1;
        checkedRelativeLayouts = new CheckedRelativeLayout2[content.length];
        checkedRelativeLayouts[0] = createLeftView(content[0]);
        checkedRelativeLayouts[lastIndex] = createRightView(content[lastIndex]);
        for (int i = 1; i < lastIndex; i++) {
            checkedRelativeLayouts[i] = createCenterView(content[i]);
        }
        for (View view : checkedRelativeLayouts) {
            LayoutParams llp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
            llp.weight = 1;
            addView(view, llp);
        }
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int i) {
        if (i < 0)
            return;
        checkedRelativeLayouts[i].setChecked(true);
    }

    public void setTextColorN(int textColorN) {
        this.textColorN = textColorN;
    }

    public void setTextColorP(int textColorP) {
        this.textColorP = textColorP;
    }

    protected CheckedRelativeLayout.OnCheckedChangeListener checkedChangeListener = new CheckedRelativeLayout.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CheckedRelativeLayout layout, boolean isChecked) {
            if (isChecked) {
                for (CheckedRelativeLayout2 item : checkedRelativeLayouts) {
                    if (!item.equals(layout)) {
                        item.setChecked(false);
                    }
                }
                if (onIndexChangedListener != null) {
                    int i = indexOf(checkedRelativeLayouts, layout);
                    index = i;
                    if (onIndexChangedListener != null) {
                        onIndexChangedListener.onChanged(SegmentView.this, index);
                    }
                }
            }
        }
    };

    protected CheckedRelativeLayout2 createLeftView(View contentView) {
        CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
        layout.setBackgroundResource(leftBg);
        layout.setGravity(Gravity.CENTER);
        layout.addView(contentView);
        layout.setOnCheckedChangeListener(checkedChangeListener);
        return layout;
    }

    protected CheckedRelativeLayout2 createCenterView(View contentView) {
        CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
        layout.setBackgroundResource(centerBg);
        layout.setGravity(Gravity.CENTER);
        layout.addView(contentView);
        layout.setOnCheckedChangeListener(checkedChangeListener);
        return layout;
    }

    protected CheckedRelativeLayout2 createRightView(View contentView) {
        CheckedRelativeLayout2 layout = new CheckedRelativeLayout2(getContext());
        layout.setBackgroundResource(rightBg);
        layout.setGravity(Gravity.CENTER);
        layout.addView(contentView);
        layout.setOnCheckedChangeListener(checkedChangeListener);
        return layout;
    }

    public void setOnIndexChangedListener(OnIndexChangedListener l) {
        this.onIndexChangedListener = l;
    }

    protected class CheckedRelativeLayout2 extends CheckedRelativeLayout {

        protected TextView textView;

        public CheckedRelativeLayout2(Context context) {
            super(context);
        }

        @Override
        public void addView(View child) {
            super.addView(child);
            if (child instanceof TextView) {
                textView = (TextView) child;
            }
        }

        @Override
        public void setChecked(boolean checked) {
            super.setChecked(checked);
            if (textView != null) {
                if (checked) {
                    textView.setTextColor(textColorP);
                } else {
                    textView.setTextColor(textColorN);
                }
            }
        }
    }

    public static interface OnIndexChangedListener {
        public void onChanged(SegmentView view, int index);
    }

    public static  int indexOf(T[] array, T obj) {
        for (int i = 0, len = array.length; i < len; i++) {
            if (array[i].equals(obj))
                return i;
        }
        return -1;
    }
}

  該方法比較簡陋,背景顏色定制性不高。即只能通過既定drawable北京來實現。不過,其實是可以通過selector來定義相關背景drawable的。不妨試一下。
  

3.2 自定義View實現

  本來此方法只是簡單提及的一個想法而已,今天有空就一並寫了。時間匆忙,代碼稍微有些混亂,不過還是能起到一定示范效用的,這裡也貼出來供大家參考。
  整體思路

定義子item 設置其選中狀態和字體/背景色。通過測量方法保證顯示范圍和字體大小,通過GradientDrawable繪制圓角背景,並畫對應字體。

定義Segment 繼承自ViewGroup,讀取自定義屬性,根據文本內容添加子View。然後重寫OnMeasure方法和OnLayout方法來測量和布局子View。最後添加點擊事件,提供監聽接口。

  代碼如下:


import com.qiao.demo.R;
import com.qiao.demo.R.styleable;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;

public class SegmentView extends ViewGroup implements OnClickListener{
    private final float r = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    private int bgColor = 0xff0072c6;
    private int fgColor = Color.WHITE;
    private float mTextSize = 3f*r;
    private String []mText= {item1,item2,item3};

    private int checkedItem=1;
    private OnItemClickListener listener;

    public SegmentView(Context context) {
        super(context);
        initFromAttributes(context, null);
        initalize();
    }

    public SegmentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initFromAttributes(context,attrs);
        initalize();
    }

    protected void initFromAttributes(Context context, AttributeSet attrs) {
        if(attrs==null) return;
        TypedArray a = context.obtainStyledAttributes(attrs,  R.styleable.SegmentView0);
        String content = a.getString(R.styleable.SegmentView0_content0);
        if(!isEmpty(content)){
            mText = content.split(,);
        }
        checkedItem = a.getInt(R.styleable.SegmentView0_index0, checkedItem);
        mTextSize = a.getDimension(R.styleable.SegmentView0_textSize0, mTextSize);
        bgColor = a.getColor(R.styleable.SegmentView0_bgColor, bgColor);
        fgColor = a.getColor(R.styleable.SegmentView0_textColor, fgColor);
        a.recycle();
    }

    public void initalize(){
        int length = mText.length;
        for(int i=0;i=0){
            maxWidth = widthSize/count;
            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth,widthMode);
        }

        for(int i=0;i=0){
                float textSize = Math.min(mTextSize,height-2*r);
                if(width>0){
                    textSize = Math.min(textSize,(width-2*r)*2/text.length()); //英文比中文短(中文為兩個字符),故取mText.length()/2作為平均寬度
                }
                if(textSize != mTextSize ){
                    mTextPaint.setTextSize(textSize);
                    mTextPaint.getTextBounds(text, 0, text.length(), mTextBound);
                }
            }
        }

        @Override
        public void draw(Canvas canvas) {
            Rect rect = canvas.getClipBounds();
            drawable.setBounds(new Rect(rect));
            drawable.draw(canvas);
            int l = (rect.width() - mTextBound.width())/2;
            int b = (rect.height() + mTextBound.height())/2;
            canvas.drawText(text, l, b, mTextPaint);
        }

    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener){
        this.listener = onItemClickListener;
    }

    interface OnItemClickListener{
        void onItemClick(ItemView item,int checkedItem);
    }

    public static boolean isEmpty(String str){
        return null==str || str.trim().length() == 0;
    }
}

  參照前面兩段講述完全可以理解了。使用時候可以方便的通過自定義屬性來控制字體顏色和點擊背景。可以動態變更View高寬。有問題的同學可以在文末提出或指正。

3.總結

  感覺自己學習進步的速度很慢,常常伴隨著焦急浮躁。這篇文章也是積累了好久才慢吞吞的寫完了。代碼方面,個人也有不少不良習慣,助事業不夠清晰,不過總體上不是有礙觀瞻吧。
  同樣的東西,嘗試用不同想法寫兩遍,我覺得是有好處的。至少於我,能看到不少有意思的東西。
  
  最後, 附上本文的 示例源碼 . 由於資源上傳較早,第二部分的自定義View並沒有打包上傳。不過上便已經貼出完整代碼了,可以直接拿來使用。

  後邊在考慮是寫一寫非UI層面的東西,還是繼續寫關於常見的增刪改UI界面。待定,總之,fighting..

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved