自定义View的尝试-A到Z快速搜索sideBar

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nzzl54/article/details/52193011

开始之前先来一段我觉得写得很好的话,相信自己才能在开发之路越走越远!


很多大神都说自定义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);

以上就是比较简单的搜索了,更复杂的效果可以自己改代码,这里不多说了。




猜你喜欢

转载自blog.csdn.net/nzzl54/article/details/52193011