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

自定义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" />

最终效果如下图:
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言并肃

感谢大哥支持!您的鼓励是我动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值