【Android】自定义View —— 滑动的次数选择器

【关键词】

自定义View 次数选择器 滑动


【问题】

  • 实现一个可滑动的次数选择器;

【效果图】

「原型图」
pic_count_view_original.jpg
「实现图」
pic_count_view.gif

【分析】

  • 对外提供简单的Change监听接口;
  • 如果处于两者之间就需要做判断:大于一半就自动跳转到下一个,小于一半,则回到上一个;
  • 通过Scroller及其startScroll()方法来实现回弹效果;
  • 要灵活控制刻度的最小值和最大值,因为可能随着需求的更改,这个值很容易发生改变(即处理好对应关系);
  • 在自定义View中只画刻度和文字,至于红色的指针和外面的透明渐变图层则可以直接在布局文件中实现:
    一方面比起在java代码中更容易实现,另一方面更灵活(如果想要去掉渐变图层,只需要修改布局文件,而不需要对java代码进行任何修改);

【解决方案】

  • 我最开始是通过不断的设置padding来实现,但是一时头脑不清晰,没有处理好对应关系,就重新改用scroll来实现了;

【代码】

「客户端用法代码」
布局代码activity_count_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="#ffffff"
    android:orientation="vertical">
    <!-- 刻度 -->
    <com.lyloou.android.view.CountView
        android:id="@+id/cv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_marginEnd="48dp"
        android:layout_marginStart="48dp"
        android:background="#ffffff" />

    <!-- 中间的指针 -->
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@mipmap/reminder_slider" />

    <!-- 遮罩层 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:orientation="horizontal">
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:background="#ffffff" />
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4"
            android:background="@drawable/gradient_count_view" />
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:background="#ffffff" />
    </LinearLayout>

</RelativeLayout>

Activity对布局文件的初始化

public class CountViewActivity extends AppCompatActivity {
    private Activity mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        setContentView(R.layout.activity_count_view);
        initView();
    }

    private void initView() {
        CountView countView = (CountView) findViewById(R.id.cv_main);
        if (countView != null) {
            // int index = countView.getIndex(); // 获取当前值
            // countView.setIndex(6);// 设置默认值
            countView.setOnChangeListener(new CountView.OnChangeListener() {
                @Override
                public void doIndex(int index) {
                    Utoast.show(mContext, "选中了:" + index + "次");
                }
            });
        }
    }
}

中间的渐变shape gradient_count_view.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <gradient
        android:angle="180"
        android:centerColor="#10ffffff"
        android:endColor="#ffffff"
        android:startColor="#ffffff" />

</shape>

「通用源代码」
自定义View

package com.lyloou.android.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import com.lyloou.android.util.Uscreen;


public class CountView extends View {

    public interface OnChangeListener {
        void doIndex(int index);
    }

    private final Context CONTEXT = getContext();
    private final int W = Uscreen.dp2Px(CONTEXT, 36);// 每一项的固定长度
    private final int LINE_H = Uscreen.dp2Px(CONTEXT, 6); // 长直线的厚度
    private final int ITEM_LINE_W = LINE_H / 3; // 竖直刻度直线的厚度
    private final int ITEM_LINE_H = Uscreen.dp2Px(CONTEXT, 16); // 竖直刻度直线的高度
    private final int BOTTOM_FONT_SIZE = (int) Uscreen.sp2Px(CONTEXT, 12); // 底部的文字大小

    private static final int COLOR_LINE = Color.parseColor("#4422f9"); // 长直线和刻度线的颜色
    private static final int MIN_ITEM = 1; // 显示的最小刻度值
    private static final int MAX_ITEM = 20; // 显示的最大刻度值
    private static final int EXTRA_COUNT = 2; // 左边和右边额外的刻度个数

    private Paint mLinePaint;
    private TextPaint mTextPaint;
    private Scroller mScroller;
    private OnChangeListener mChangeListener;

    public CountView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setUp();
        initData();
    }


    public CountView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void setUp() {
        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setColor(COLOR_LINE);

        mTextPaint = new TextPaint();
        mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(BOTTOM_FONT_SIZE);

        mScroller = new Scroller(CONTEXT);
    }

    private void initData() {
        setIndex(MIN_ITEM);
    }

    private int mLastX;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                int x = (int) event.getX();
                int deltaX = x - mLastX;
                scrollBy(-deltaX, 0);
                mLastX = x;
                break;
            case MotionEvent.ACTION_UP:
                doIndex(getIndex());
                break;
        }
        return true;
    }

    private void doIndex(int index) {
        if (mChangeListener != null) {
            mChangeListener.doIndex(index + MIN_ITEM);
        }
    }

    public int getIndex() {
        int index = 0;
        int scrollX = getScrollX();
        index = scrollX / W;
        if (scrollX <= 0) {
            index = 0;
            smoothScrollTo(index);
        } else if (index >= (MAX_ITEM - MIN_ITEM)) {
            index = MAX_ITEM - MIN_ITEM;
            smoothScrollTo(index * W);
        } else {
            int shift = scrollX % W;
            if (shift >= W / 2) {
                index++;
                smoothScrollBy(W - shift);
            } else {
                smoothScrollBy(-shift);
            }
        }
        return index;
    }

    public void setIndex(int index) {
        int newIndex = index - MIN_ITEM;
        scrollTo(newIndex * W, 0);
    }

    public void setOnChangeListener(OnChangeListener listener) {
        mChangeListener = listener;
    }

    private void smoothScrollTo(int dextX) {
        smoothScrollBy(dextX - getScrollX());
    }

    private void smoothScrollBy(int deltaX) {
        mScroller.startScroll(getScrollX(), 0, deltaX, 0, 320);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller != null && mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int shift = getWidth() / 2 - W / 2; // 将原点移到屏幕中间;
        int height = getHeight();

        for (int i = MIN_ITEM - EXTRA_COUNT; i < MAX_ITEM + EXTRA_COUNT; i++) {
            int startX = (i - MIN_ITEM) * W + shift;

            // 画直线
            mLinePaint.setStrokeWidth(LINE_H);
            canvas.drawLine(startX, height / 2, startX + W, height / 2, mLinePaint);

            // 画刻度
            mLinePaint.setStrokeWidth(ITEM_LINE_W);
            canvas.drawLine(startX + W / 2, height / 2 - ITEM_LINE_H, startX + W / 2, height / 2, mLinePaint);

            // 在刻度范围内,才画文字
            if (i >= MIN_ITEM && i <= MAX_ITEM) {
                String text = i + "次";
                float textWidth = mTextPaint.measureText(text);
                float textHeight = mTextPaint.getFontMetrics().bottom;
                canvas.drawText(text, startX + (W - textWidth) / 2, (height + textHeight) / 2 + textHeight * 10,
                        mTextPaint);
            }
        }
    }
}

【参考资料】

  • 回弹效果的代码参考了《Android开发艺术探索》P136
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值