美文网首页Android OtherAndroid 自定义view
android自定义侧边字母索引表(联系人字母索引)

android自定义侧边字母索引表(联系人字母索引)

作者: 还是做个码农 | 来源:发表于2022-10-05 22:00 被阅读0次

    自定义LetterSideBarView 侧边字母索引表如下:

    package com.example.myapplication;
    
    import android.animation.ValueAnimator;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.RectF;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    import android.view.MotionEvent;
    import android.view.View;
    
    import androidx.annotation.Nullable;
    
    import java.util.Arrays;
    import java.util.List;
    
    public class LetterSideBarView extends View {
        private static final double ANGLE_45 = Math.PI * 45 /100;
        private int mBackgroundColor;
        private int mStrokeColor;
        private int mTextColor;
        private int mTextSize;
        private int mSelectTextColor;
        private int mSelectTextSize;
        private int mHintTextColor;
        private int mHintTextSize;
        private int mHintCircleRadius;
        private int mHintCircleColor;
        private int mWaveColor;
        private int mWaveRadius;
        private int mContentPadding;
        private int mBarPadding;
        private int mBarWidth;
    
        private List<String> mLetters;
        private RectF mSlideBarRect;
        private TextPaint mTextPaint;
        private Paint mPaint;
        private Paint mWavePaint;
        private Path mWavePath;
        private int mSelect;
        private int mPreSelect;
        private int mNewSelect;
        private ValueAnimator mRatioAnimator;
        private float mAnimationRatio;
        private OnLetterChangeListener mListener;
        private int mTouchY = -1;
        private boolean mIsTouching;
        private static final boolean CONFIG_DRAW_STROKE = false; //配置是否绘制边框
        private static final boolean CONFIG_DRAW_WAVE = false; //配置是否绘制选中波纹
        public LetterSideBarView(Context context) {
            this(context, null);
        }
    
        public LetterSideBarView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public LetterSideBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initAttribute(context, attrs, defStyleAttr);
            initData();
        }
    
    
        private void initAttribute(Context context, AttributeSet attrs, int defStyleAttr) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LetterSideBarView, defStyleAttr, 0);
            mBackgroundColor = typedArray.getColor(R.styleable.LetterSideBarView_backgroundColor, getResources().getColor(android.R.color.transparent));
            mStrokeColor = typedArray.getColor(R.styleable.LetterSideBarView_strokeColor, Color.parseColor("#000000"));
            mTextColor = typedArray.getColor(R.styleable.LetterSideBarView_textColor, Color.parseColor("#969696"));
            mSelectTextColor = typedArray.getColor(R.styleable.LetterSideBarView_selectTextColor, Color.parseColor("#FFFFFF"));
            mHintTextColor = typedArray.getColor(R.styleable.LetterSideBarView_hintTextColor, Color.parseColor("#FFFFFF"));
            mHintCircleColor = typedArray.getColor(R.styleable.LetterSideBarView_hintCircleColor, Color.parseColor("#bef9b81b"));
            mWaveColor = typedArray.getColor(R.styleable.LetterSideBarView_waveColor, Color.parseColor("#bef9b81b"));
            mTextSize = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_textSize,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics()));
            mSelectTextSize = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_selectTextSize,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics()));
            mHintTextSize = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_hintTextSize,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
            mHintCircleRadius = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_hintCircleRadius,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
            mWaveRadius = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_waveRadius,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics()));
            mContentPadding = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_contentPadding,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()));
            mBarPadding = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_barPadding,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics()));
            mBarWidth = typedArray.getDimensionPixelOffset(R.styleable.LetterSideBarView_barWidth,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, getResources().getDisplayMetrics()));
            if (mBarWidth == 0) {
                mBarWidth = 2 * mTextSize;
            }
            typedArray.recycle();
        }
    
    
        private void initData() {
            mLetters = Arrays.asList(getContext().getResources().getStringArray(R.array.side_bar_value_list));
            mTextPaint = new TextPaint();
            mTextPaint.setAntiAlias(true);
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mWavePaint = new Paint();
            mWavePaint.setAntiAlias(true);
            mWavePath = new Path();
            mSelect = -1;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if (mSlideBarRect == null) {
                mSlideBarRect = new RectF();
            }
            float contentLeft = getMeasuredWidth() - mBarWidth - mBarPadding;
            float contentRight = getMeasuredWidth() - mBarPadding;
            float contentTop = mBarPadding;
            float contentBottom = getMeasuredHeight() - mBarPadding;
            mSlideBarRect.set(contentLeft, contentTop, contentRight, contentBottom);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //绘制slide bar上的字母列表
            drawLetters(canvas);
            //绘制选中时的波纹/圆形效果
            if (CONFIG_DRAW_WAVE) {
                drawWave(canvas);
            }
            //绘制选中时的提示信息(圆+文字)
            drawHint(canvas);
            //绘制选中时的slidebar上的文字
            drawSelect(canvas);
    
        }
    
        /**
         * 绘制Slide bar上的字母列表
         */
        private void drawLetters(Canvas canvas) {
            //绘制圆角矩形
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mBackgroundColor);
            canvas.drawRoundRect(mSlideBarRect, mBarWidth / 2.0f, mBarWidth / 2.0f, mPaint);
            //绘制描边
            if (CONFIG_DRAW_STROKE) {
                mPaint.setStyle(Paint.Style.STROKE);
                mPaint.setColor(mStrokeColor);
                canvas.drawRoundRect(mSlideBarRect, mBarWidth / 2.0f, mBarWidth / 2.0f, mPaint);
            }
            //绘制文字
            float itemHeight = (mSlideBarRect.bottom - mSlideBarRect.top - mContentPadding * 2) / mLetters.size();
            for (int index = 0; index < mLetters.size(); index++) {
                float baseLine = getTextBaseLineByCenter(mSlideBarRect.top + mContentPadding + itemHeight * index + itemHeight / 2,
                        mTextPaint, mTextSize);
                mTextPaint.setColor(mTextColor);
                mTextPaint.setTextSize(mTextSize);
                mTextPaint.setTextAlign(Paint.Align.CENTER);
                float pointX = mSlideBarRect.left + (mSlideBarRect.right - mSlideBarRect.left) / 2.0f;
                canvas.drawText(mLetters.get(index), pointX, baseLine, mTextPaint);
            }
        }
    
        /**
         * 绘制选中时的波纹效果
         */
        private void drawWave(Canvas canvas) {
            mWavePath.reset();
            //移动到起始点
            int startX = getMeasuredWidth();
            int startY = mTouchY - 3 * mWaveRadius;
            mWavePath.moveTo(startX, startY);
            //计算上部控制点的Y轴位置
            int topControlX = getMeasuredWidth();
            int topControlY = mTouchY - 2 * mWaveRadius;
            int topEndX = (int) (getMeasuredWidth() - mWaveRadius * Math.cos(ANGLE_45) * mAnimationRatio);
            int topEndY = (int) (topControlY + mWaveRadius * Math.sin(ANGLE_45));
            mWavePath.quadTo(topControlX, topControlY, topEndX, topEndY);
            //计算中心控制点的坐标
            int centerControlX = (int) (getMeasuredWidth() - 1.0f * mWaveRadius * mAnimationRatio);
            int centerControlY = mTouchY;
            int centerEndX = topEndX;
            int centerEndY = (int) (mTouchY + 2 * mWaveRadius - mWaveRadius * Math.cos(ANGLE_45));
            mWavePath.quadTo(centerControlX, centerControlY, centerEndX, centerEndY);
            //计算下部借宿点的坐标
            int bottomEndX = getMeasuredWidth();
            int bottomEndY = mTouchY + 3 * mWaveRadius;
            int bottomControlX = getMeasuredWidth();
            int bottomControlY = mTouchY + 2 * mWaveRadius;
            mWavePath.quadTo(bottomControlX, bottomControlY, bottomEndX, bottomEndY);
            mWavePath.close();
            mWavePaint.setStyle(Paint.Style.FILL);
            mWavePaint.setColor(mWaveColor);
            canvas.drawPath(mWavePath, mWavePaint);
        }
    
        /**
         * 绘制选中时的提示文字(圆+文字)
         */
        private void drawSelect(Canvas canvas) {
            if (mSelect != -1) {
                mTextPaint.setColor(mSelectTextColor);
                mTextPaint.setTextSize(mSelectTextSize);
                mTextPaint.setTextAlign(Paint.Align.CENTER);
                mWavePaint.setStyle(Paint.Style.FILL);
                mWavePaint.setColor(mHintCircleColor);
                float itemHeight = (mSlideBarRect.bottom - mSlideBarRect.top - mContentPadding * 2) / mLetters.size();
                float baseLine = getTextBaseLineByCenter(mSlideBarRect.top + mContentPadding + itemHeight * mSelect + itemHeight / 2,
                        mTextPaint, mTextSize);
                float pointX = mSlideBarRect.left + (mSlideBarRect.right - mSlideBarRect.left) / 2.0f;
                //绘制选中圆形
                canvas.drawCircle(pointX, itemHeight * (mSelect + 1) - mContentPadding * 2, mTextSize / 2.0f + 1, mWavePaint);
                canvas.drawText(mLetters.get(mSelect), pointX, baseLine, mTextPaint);
            }
        }
    
        /**
         * 绘制选中的Slide Bar上的文字
         */
        private void drawHint(Canvas canvas) {
            //x轴的移动路径
            if (mSelect != -1 && mTouchY != -1) {
                float circleCenterX = (getMeasuredWidth() + mHintCircleRadius) - (2.0f * mWaveRadius + 2.0f * mHintCircleRadius) * mAnimationRatio - 17;
                mWavePaint.setStyle(Paint.Style.FILL);
                mWavePaint.setColor(mHintCircleColor);
                canvas.drawCircle(circleCenterX, mTouchY, mHintCircleRadius, mWavePaint);
                //绘制提示字符
                if (mAnimationRatio >= 0.9f && mSelect != -1) {
                    String target = mLetters.get(mSelect);
                    float textY = getTextBaseLineByCenter(mTouchY, mTextPaint, mHintTextSize);
                    mTextPaint.setColor(mHintTextColor);
                    mTextPaint.setTextSize(mHintTextSize);
                    mTextPaint.setTextAlign(Paint.Align.CENTER);
                    canvas.drawText(target, circleCenterX, textY, mTextPaint);
                }
            }
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            mPreSelect = mSelect;
            mNewSelect = (int) (y / (mSlideBarRect.bottom - mSlideBarRect.top) * mLetters.size());
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mIsTouching = true;
                    mTouchY = (int) y;
                    startAnimation(1.0f);
                    break;
                case MotionEvent.ACTION_MOVE:
                    mTouchY = (int) y;
                    if (mPreSelect != mNewSelect && mNewSelect >= 0 && mNewSelect < mLetters.size()) {
                        mSelect = mNewSelect;
                        if (mListener != null) {
                            mListener.onLetterChange(mLetters.get(mNewSelect));
                        }
                    }
                    invalidate();
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mIsTouching = false;
                    startAnimation(0f);
                    mTouchY = -1;
                    break;
    
            }
            return true;
        }
    
        private void startAnimation(float value) {
            if (mRatioAnimator == null) {
                mRatioAnimator = new ValueAnimator();
            }
            mRatioAnimator.cancel();
            mRatioAnimator.setFloatValues(value);
            mRatioAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mAnimationRatio = (float) animation.getAnimatedValue();
                    if (mAnimationRatio == 1f && mPreSelect != mNewSelect) {
                        if (mNewSelect >= 0 && mNewSelect < mLetters.size()) {
                            mSelect = mNewSelect;
                            if (mListener != null) {
                                mListener.onLetterChange(mLetters.get(mNewSelect));
                            }
                        }
                    }
                    invalidate();
                }
            });
            mRatioAnimator.start();
        }
    
    
        /**
         * 判断当前是否在触摸状态
         */
        public boolean isTouching() {
            return mIsTouching;
        }
    
        /**
         * 给定文字的center获取文字的baseLine
         */
        private float getTextBaseLineByCenter(float center, TextPaint paint, int size) {
            paint.setTextSize(size);
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            float height = fontMetrics.bottom - fontMetrics.top;
            return center + height / 2 - fontMetrics.bottom;
        }
    
        public void setOnLetterChangeListener(OnLetterChangeListener listener) {
            this.mListener = listener;
        }
    
        public interface OnLetterChangeListener {
            void onLetterChange(String letter);
        }
    }
    
    

    自定义View的属性attrs.xml中的代码如下

        <declare-styleable name="LetterSideBarView">
            <!--有效内容背景-->
            <attr name="backgroundColor" format="color"/>
            <!--描边-->
            <attr name="strokeColor" format="color"/>
            <!--文字颜色-->
            <attr name="textColor" format="color"/>
            <!--文字大小-->
            <attr name="textSize" format="dimension"/>
            <!--选中文字颜色-->
            <attr name="selectTextColor" format="color"/>
            <!--选中文字大小-->
            <attr name="selectTextSize" format="dimension"/>
            <!--选中时提示文字颜色-->
            <attr name="hintTextColor" format="color"/>
            <!--选中时提示文字大小-->
            <attr name="hintTextSize" format="dimension"/>
            <!--选中时提示圆形半径-->
            <attr name="hintCircleRadius" format="dimension"/>
            <!--选中时提示圆形颜色-->
            <attr name="hintCircleColor" format="color"/>
            <!--选中时波浪颜色-->
            <attr name="waveColor" format="color"/>
            <!--选中时波浪半径-->
            <attr name="waveRadius" format="dimension"/>
            <!--里面文字上+下padding-->
            <attr name="contentPadding" format="dimension"/>
            <!--上+下+右padding-->
            <attr name="barPadding" format="dimension"/>
            <!--宽度-->
            <attr name="barWidth" format="dimension"/>
        </declare-styleable>
    

    显示字母数组arrays.xml如下

    <string-array name="side_bar_value_list">
            <item>A</item>
            <item>B</item>
            <item>C</item>
            <item>D</item>
            <item>E</item>
            <item>F</item>
            <item>G</item>
            <item>H</item>
            <item>I</item>
            <item>J</item>
            <item>K</item>
            <item>L</item>
            <item>M</item>
            <item>N</item>
            <item>O</item>
            <item>P</item>
            <item>Q</item>
            <item>R</item>
            <item>S</item>
            <item>T</item>
            <item>U</item>
            <item>V</item>
            <item>W</item>
            <item>X</item>
            <item>Y</item>
            <item>Z</item>
            <item>\u0023</item><!-- #-->
        </string-array>
    

    布局文件中的代码如下:

        <com.example.myapplication.LetterSideBarView
            android:id="@+id/sbv_letter"
            android:layout_width="wrap_content"
            android:layout_height="700dp"
            android:layout_centerVertical="true"
            app:hintCircleColor="#26FF00"
            app:hintCircleRadius="36dp"
            app:hintTextColor="@android:color/white"
            app:hintTextSize="48sp"
            app:textColor="#6B7513"
            android:layout_alignParentEnd="true"
            app:textSize="20sp" />
    

    最终效果如下图:


    ea872d7e37eb43929ce0a4a4ed59b39f.png

    相关文章

      网友评论

        本文标题:android自定义侧边字母索引表(联系人字母索引)

        本文链接:https://www.haomeiwen.com/subject/fagpartx.html