1. 程式人生 > >自定義View的嘗試-A到Z快速搜尋sideBar

自定義View的嘗試-A到Z快速搜尋sideBar

開始之前先來一段我覺得寫得很好的話,相信自己才能在開發之路越走越遠!


很多大神都說自定義View是進階的必經之路,那麼自然是要學習的。

自定義View的實現方式大概可以分為三種:(View這裡不做詳細介紹:請看文章

1.自繪控制元件:這個View上所展現的內容全部都是我們自己繪製出來的

2.組合控制元件:將幾個系統原生的控制元件組合到一起(例子:略)

3.繼承控制元件:只需要去繼承一個現有的控制元件,然後在這個控制元件上增加一些新的功能,就可以形成一個自定義的控制元件了(例子:自定義PopupWindow--可擴充套件操作


這裡介紹第一種“自繪控制元件”,先來個簡單的例子瞭解一下,後面再深入講解SideBar,效果如下:


1、設定屬性在attrs.xml , 定義屬性和宣告整個樣式,控制元件就叫TestView,可以設定的屬性有背景顏色,字型顏色,和文字內容

<declare-styleable name="TestView">
        <attr name="mbackgroundColor" format="color"/>
        <attr name="mtextColor" format="color"/>
        <attr name="mtext" format="string"/>
    </declare-styleable>

2、在佈局中使用

<com.example.lanzheng.search.TestView
        android:id="@+id/test"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        app:mtext="testtesttest"
        app:mbackgroundColor="#00ffbf"
        app:mtextColor="#3900a4"/>

3、自定義View的編寫

package com.example.lanzheng.search;

import android.annotation.TargetApi;
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.os.Build;
import android.util.AttributeSet;
import android.view.View;

import com.example.lanzheng.wheeldemo.R;

/**
 * Created by lan.zheng on 2016/8/18.
 */
public class TestView extends View{

    /**
     * 文字的顏色
     */
    private int mTitleTextColor;
    /**
     * 文字的背景顏色
     */
    private int mTitleTextBackground;
    /**
     * 文字
     */
    private String mTitleText;

    //繪製範圍
    private Paint mPaint;
    private Rect mBound;
    public TestView(Context context) {
        super(context);
    }

    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //獲得我們所定義的自定義樣式屬性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TestView);
        //獲取自定義的屬性數量,根據不的屬性來賦值
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.TestView_mbackgroundColor:
                    mTitleTextBackground = array.getColor(R.styleable.TestView_mbackgroundColor, Color.WHITE);//預設背景顏色
                    break;
                case R.styleable.TestView_mtextColor:
                    // 預設顏色設定為黑色
                    mTitleTextColor = array.getColor(R.styleable.TestView_mtextColor, Color.BLACK);  //預設字型顏色
                    break;
                case R.styleable.TestView_mtext:
                    mTitleText = array.getString(attr);  //獲得文字
                    break;
            }
        }
        array.recycle();
        //初始化Paint,為繪畫做準備
        mPaint = new Paint();
        //設定文字邊界
        mBound = new Rect();
        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        //繪畫,背景
        mPaint.setColor(mTitleTextBackground);
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
        //繪畫,文字顏色和內容
        mPaint.setColor(mTitleTextColor);
        canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        //重寫該方法用於適配寬和高
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height ;
        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
            float textWidth = mBound.width();
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = desired;
        }

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
            float textHeight = mBound.height();
            int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = desired;
        }



        setMeasuredDimension(width, height);
    }

}

接下來對自定義View有了認識,我們來做快速檢索的SideBar,效果如下:


1.自定義View的編寫,這裡直接在View中固定想要的顏色和文字,所以不需要再attrs.xml加入什麼

package com.example.lanzheng;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

/**
 * 字母條view
 * 快速檢索
 */
public class LetterSideBar extends View {
	// 觸控事件
	private OnTouchingLetterChangedListener onTouchingLetterChangedListener;
	// 26個字母
	public static String[] b = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
			"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
			"W", "X", "Y", "Z", "#"};
	//預設沒有選中的,非選中為-1
	private int choose = -1;
	//畫筆,設定
	private Paint paint = new Paint();
       //選中文字顯示View
	private TextView mTextDialog;

	public void setTextView(TextView mTextDialog) {
		this.mTextDialog = mTextDialog;
	}

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

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

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

	/**
	 * 重寫這個方法
	 */
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		int height = getHeight();// 獲取對應高度
		int width = getWidth(); // 獲取對應寬度
		int singleHeight = height / b.length;// 獲取每一個字母的高度
        // 獲取焦點改變背景顏色.
		for (int i = 0; i < b.length; i++) {
//			paint.setColor(Color.rgb(3,123,254));
			// paint.setColor(Color.WHITE);
			paint.setColor(Color.parseColor("#43CD80"));  //預設顏色green
			paint.setTypeface(Typeface.DEFAULT_BOLD);
			paint.setAntiAlias(true);
			paint.setTextSize(20);
			// 選中的狀態
			if (i == choose) {
				paint.setColor(Color.parseColor("#228B22"));  //選中的顏色gray green
				paint.setFakeBoldText(true);
			}
			// x座標等於中間-字串寬度的一半.
			float xPos = width / 2 - paint.measureText(b[i]) / 2;
			float yPos = singleHeight * i + singleHeight;
			canvas.drawText(b[i], xPos, yPos, paint);
			paint.reset();// 重置畫筆
		}

	}

	/**
	 * 重寫觸控事件
	 * @param event
	 * @return
     */
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		final int action = event.getAction();
		final float y = event.getY();// 點選y座標
		final int oldChoose = choose;
		final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;  //拿到例項
		final int c = (int) (y / getHeight() * b.length);// 點選y座標所佔總高度的比例*b陣列的長度就等於點選b中的個數.

		switch (action) {
		case MotionEvent.ACTION_UP:
			//setBackgroundDrawable(new ColorDrawable(0x00000000));  
			choose = -1;   //擡起 = 1 ,MotionEvent.ACTION_UP
			invalidate();  //重繪
			if (mTextDialog != null) {
				mTextDialog.setVisibility(View.INVISIBLE);
			}
			break;

		default:
			//setBackgroundResource(R.drawable.sidebar_background);
			if (oldChoose != c) {
				if (c >= 0 && c < b.length) {
					if (listener != null) {
						listener.onTouchingLetterChanged(b[c]);//回撥,返回當前選中的字元
					}
					if (mTextDialog != null) {
						mTextDialog.setText(b[c]);
						mTextDialog.setVisibility(View.VISIBLE);
					}
					
					choose = c;
					invalidate();  //重繪
				}
			}

			break;
		}
		return true;
	}

	/**
	 * 向外公開的方法
	 * @param onTouchingLetterChangedListener
	 */
	public void setOnTouchingLetterChangedListener(
			OnTouchingLetterChangedListener onTouchingLetterChangedListener)
	{
		this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
	}

	/**
	 * 介面
	 * @author coder
	 */
	public interface OnTouchingLetterChangedListener {
		public void onTouchingLetterChanged(String s);
	}

}
2.佈局中使用activity_search.xml

 <FrameLayout
        android:paddingTop="10dp"
        android:id="@+id/layout_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_marginTop="75dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>

        <TextView
            android:id="@+id/dialog"
            android:layout_width="80.0dip"
            android:layout_height="80.0dip"
            android:layout_gravity="center"
            android:background="@drawable/show_head_toast_bg"
            android:gravity="center"
            android:textColor="#ffffffff"
            android:textSize="30.0dip"
            android:visibility="invisible" /><!--用於指示選中字母-->

        <com.example.lanzheng.LetterSideBar
            android:id="@+id/sidrbar"
            android:layout_width="35dp"
            android:layout_height="match_parent"
            android:layout_gravity="right|center" />
    </FrameLayout>

3.Activity中進行初始化和監聽

private TextView letterTextView;
    private LetterSideBar mLetterSideBar;
    private RecyclerView mRecyclerView;

    private void initView(){
         letterTextView = (TextView)findViewById(R.id.dialog) ;
        mLetterSideBar = (LetterSideBar) findViewById(R.id.sidrbar);
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mLetterSideBar.setTextView(letterTextView);  //設定顯示選中顯示的View
        mLetterSideBar.setOnTouchingLetterChangedListener(new LetterSideBar.OnTouchingLetterChangedListener() {
            @Override
            public void onTouchingLetterChanged(String s) {
                // 該字母首次出現的位置
                Log.d("test","s = "+s);
                int position = mListAdapter.getPositionForSection(s.charAt(0));  //在Adapter中去獲得要移動到的位置,Adatper內容請自定義
                if (position != -1) {
                    //mIndex = position;  //獲得position
                    //moveToPosition(lLinearLayoutManager);  //滑動效果,此處暫時不用,需要配合addOnScrollListener使用
                   lLinearLayoutManager.scrollToPositionWithOffset(position,0); //無效果的滑動
                }
            }
        });
    }

感覺這個有點簡單了是吧,要顯示的陣列是定死的,那我們就改改程式碼吧,這裡預設沒有給資料的時候顯示預設為居中的“空”字,當字元小於20,就不給他那麼大的空間,小於21個搜尋字元時就居中,並且增加了點選後背景的效果,效果如下所示:


程式碼如下:

/**
 * Created by lan.zheng on 2016/9/23.
 */
public class LetterSideBar extends View {
    // 觸控事件
    private OnTouchingLetterChangedListener onTouchingLetterChangedListener;

    public static String[] b = {"空"}; //預設給一個,防止丟擲異常
    public int stringLength = 1;  //陣列長度
    public static int itemHeight = 50;  //小於21個時候的高度定死
    public static int itemNumAbove = 20;  //用於超出20判斷
    int beginPosition = 0;    //開始位置
    int totalHeight = 0;   //搜尋有字的高度
    //預設沒有選中的,非選中為-1
    private int choose = -1;
    //畫筆,設定
    private Paint paint = new Paint();
    //選中文字顯示View
    private TextView mTextDialog;

    public void setTextView(TextView mTextDialog) {
        this.mTextDialog = mTextDialog;
    }

    public void setStrings(String[] strings){
        b  = strings;
        stringLength = b.length;
        if(stringLength > itemNumAbove){
            totalHeight = getHeight();
        }else {
            totalHeight = itemHeight;
        }
    }

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

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

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

    /**
     * 重寫這個方法
     */
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int height = getHeight();// 獲取對應高度
        int width = getWidth(); // 獲取對應寬度
        int singleHeight;

        if(stringLength > itemNumAbove){
            singleHeight = height / stringLength;// 獲取每一個字母的高度
        }else {
            singleHeight = itemHeight;
            beginPosition = height/2 - singleHeight*stringLength/2;
        }
        // 獲取焦點改變背景顏色.
        for (int i = 0; i < stringLength; i++) {
//          paint.setColor(Color.rgb(3,123,254));
            // paint.setColor(Color.WHITE);
            paint.setColor(Color.parseColor("#43CD80"));  //預設顏色green
            paint.setTypeface(Typeface.DEFAULT_BOLD);
            paint.setAntiAlias(true);
            paint.setTextSize(20);
            // 選中的狀態
            if (i == choose) {
                paint.setColor(Color.parseColor("#228B22"));  //選中的顏色gray green
                paint.setFakeBoldText(true);
            }
            float xPos = width / 2 - paint.measureText(b[i]) / 2;
            float yPos = beginPosition + singleHeight*i + singleHeight/2;
            canvas.drawText(b[i], xPos, yPos, paint);
            paint.reset();// 重置畫筆
        }

    }

    /**
     * 重寫觸控事件
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        final float y = event.getY();// 點選y座標

        if(y < beginPosition || y > getHeight() - beginPosition){  //非點選位置的不做處理
            setBackgroundDrawable(new ColorDrawable(0x00000000));
            choose = -1;
            invalidate();  //重繪
            if (mTextDialog != null) {
                mTextDialog.setVisibility(View.INVISIBLE);
            }
        }else {
            final int oldChoose = choose;
            final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;  //拿到例項
            // 獲取點選了第幾個
//            final int c2 = (int) (y / getHeight() * b.length);
            int sigleItemHight = itemHeight;
            if(stringLength > itemNumAbove){
                sigleItemHight = getHeight()/stringLength;
            }
            final int c = (int) ((y - beginPosition)/sigleItemHight);  //點選的位置-開始的位置,得到了實際移動的位置,總位置除以它就能得到點選的字母是什麼
            switch (action) {
                case MotionEvent.ACTION_UP:
                    setBackgroundDrawable(new ColorDrawable(0x00000000));
                    choose = -1;   //擡起 = 1 ,MotionEvent.ACTION_UP
                    invalidate();  //重繪
                    if (mTextDialog != null) {
                        mTextDialog.setVisibility(View.INVISIBLE);
                    }
                    break;

                default:
                    setBackgroundDrawable(new ColorDrawable(0x20000000));
                    if (oldChoose != c) {
                        if (c >= 0 && c < b.length) {
                            if (listener != null) {
                                listener.onTouchingLetterChanged(b[c]);//回撥,返回當前選中的字元
                            }
                            if (mTextDialog != null) {
                                mTextDialog.setText(b[c]);
                                mTextDialog.setVisibility(View.VISIBLE);
                            }

                            choose = c;
                            invalidate();  //重繪
                        }
                    }

                    break;
            }
        }

        return true;
    }

    /**
     * 向外公開的方法
     * @param onTouchingLetterChangedListener
     */
    public void setOnTouchingLetterChangedListener(
            OnTouchingLetterChangedListener onTouchingLetterChangedListener)
    {
        this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
    }

    /**
     * 介面
     * @author coder
     */
    public interface OnTouchingLetterChangedListener {
        public void onTouchingLetterChanged(String s);
    }

}

使用時如下即可:

letterSideBar = (LetterSideBar)findViewById(R.id.letter);
letterSideBar.setStrings(s2);

以上就是比較簡單的搜尋了,更復雜的效果可以自己改程式碼,這裡不多說了。