03.自定义View(AlphabetView字母索引View)

2018-03-25  本文已影响34人  任振铭

感谢红橙Darren博主

package com.rzm.commonlibrary.views;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

import com.rzm.commonlibrary.R;


/**
 * Created by renzhenming on 2018/3/24.
 * 实现根据字母分类排序的列表,多用于地址选择或者手机通讯录
 */

public class AlphabetView extends View{

    //普通模式绘制的画笔
    private final Paint mPaint;

    //触摸时绘制用的画笔
    private final Paint mTouchPaint;

    //默认字母大小
    private int mTextSize = 16;

    //默认字母颜色
    private int mTextColor = Color.BLACK;

    //触摸时字母颜色
    private int mTouchTextColor = Color.RED;

    //控件的高度
    private int mViewHeight;

    //26个字母
    private String mLetters[] = {"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"};
    private int mItemHeight;

    //当前触摸的字母
    private String mCurrentTouchLetter;

    //当前是否在触摸
    private boolean mCurrentIsTouch = false;

    //抬起之后是否清除当前选中的高亮颜色
    private boolean mClearAfterUp = true;

    public AlphabetView(Context context) {
        this(context,null);
    }

    public AlphabetView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,-1);
    }

    public AlphabetView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //获取自定义属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AlphabetView);
        mTextColor = typedArray.getColor(R.styleable.AlphabetView_textColor, mTextColor);
        mTouchTextColor = typedArray.getColor(R.styleable.AlphabetView_touchTextColor, mTouchTextColor);
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.AlphabetView_textSize, mTextSize);
        typedArray.recycle();

        //设置画笔用于绘制字母

        /**
         * 为什么使用两个画笔而不是单独的使用一个然后切换颜色的设置,因为paint是有一个
         * 设置颜色的方法的setColor,我们看一下setColor的源码,发现是native方法实现的,
         *  private static native void nSetColor(long paintPtr, @ColorInt int color);
         * 如果在onDraw中频繁的切换颜色性能会降低,所以这就是通常使用几种颜色定义几个画笔
         * 的原因
         */
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(sp2px(mTextSize));

        mTouchPaint = new Paint();
        mTouchPaint.setAntiAlias(true);
        mTouchPaint.setDither(true);
        mTouchPaint.setColor(mTouchTextColor);
        mTouchPaint.setTextSize(sp2px(mTextSize));
    }

    private float sp2px(int sp){
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //根据布局中设置的宽度模式确定宽度
        int width =0;
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY){
            width = MeasureSpec.getSize(widthMeasureSpec);
        }
        if (mode == MeasureSpec.AT_MOST){
            //计算宽度,两边的padding加上文字宽度,这个文字随意,不过最好是26个字母中最宽的
            float textWidth = mPaint.measureText("");
            width = (int) (getPaddingLeft() + getPaddingRight()+textWidth);
        }
        //获取设置的高度
        mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(width, mViewHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取每个字母占的高度
        mItemHeight = (getHeight()-getPaddingTop() - getPaddingBottom())/mLetters.length;

        //绘制26个字母
        for (int i = 0; i < mLetters.length; i++) {
            //得到每个字母的中心位置,防止给布局设置了paddingTop,所以这里+上
            int letterCenterY = i* mItemHeight + mItemHeight /2+getPaddingTop();

            //得到每个字母的中心位置
            Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
            int dy = (int) ((fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom);

            //根据字母中心位置求字母的基线
            //baseline = center + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom;
            int baseLine = letterCenterY+dy;

            //获取每一个字母绘制的x坐标
            float letterWidth = mPaint.measureText(mLetters[i]);
            //确保每一个字母都在中间位置
            int x = (int) (getWidth()/2 - letterWidth/2);
            //开始绘制
            /**
             * text 需要绘制的文字
             x 绘制文字原点X坐标
             y 绘制文字原点Y坐标
             xy指的时文字左下角的坐标,也就是基线和文字最左边下方的交点
             paint 画笔
             */
            if (mLetters[i].equals(mCurrentTouchLetter)){
                canvas.drawText(mLetters[i],x,baseLine,mTouchPaint);
            }else{
                canvas.drawText(mLetters[i],x,baseLine,mPaint);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:

                //当前触摸的位置,相对与控件的位置而不是相对于屏幕
                float startY = event.getY();
                //计算当前字母的位置
                int position = (int)startY/mItemHeight;

                //防止当触摸超出控件范围的时候,设定position值,防止数组越界
                if (position <0){
                    position = 0;
                }
                if (position > mLetters.length - 1){
                    position = mLetters.length - 1;
                }
                //滑动过程中如果滑动的位置和上一次相同,就不再处理,减少invalidate的次数
                if (mLetters[position].equals(mCurrentTouchLetter)){
                    return true;
                }
                //记录当前触摸的弥足
                mCurrentTouchLetter = mLetters[position];
                mCurrentIsTouch = true;
                if (mTouchListener != null){
                    mTouchListener.onTouch(mCurrentTouchLetter,mCurrentIsTouch);
                }

                break;

            case MotionEvent.ACTION_UP:
                //抬起时设置为false
                mCurrentIsTouch = false;
                if (mTouchListener != null){
                    mTouchListener.onTouch(mCurrentTouchLetter,mCurrentIsTouch);
                }
                if (mClearAfterUp){
                    //将当前触摸的字母设置为null,up之后重绘界面,清除高亮状态
                    mCurrentTouchLetter = null;
                }
                break;
        }
        invalidate();
        return true;
    }

    /**
     * 设置手指触摸抬起之后,是否恢复高亮状态为正常状态,默认为true
     * @param mClearAfterUp
     */
    public void setClearAfterUp(boolean mClearAfterUp) {
        this.mClearAfterUp = mClearAfterUp;
    }

    // 设置触摸监听
    private OnAlphabetTouchListener mTouchListener;
    public void setOnAlphabetTouchListener(OnAlphabetTouchListener touchListener) {
        this.mTouchListener = touchListener;
    }
    public interface OnAlphabetTouchListener {
        void onTouch(String letter, boolean isTouch);
    }
}
上一篇下一篇

猜你喜欢

热点阅读