• 自定义轻量级的安卓滚轮选择控件


    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    import android.view.MotionEvent;
    import android.view.VelocityTracker;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.widget.Scroller;
    
    import java.util.ArrayList;
    
    /**
     * 滚轮视图,可设置是否循环模式,实现OnScrollChangedListener接口以监听滚轮变化
     * Created by huzn on 2016/10/27.
     */
    public class EasyPickerView extends View {
    
        // 文字大小
        private int textSize;
        // 颜色,默认Color.BLACK
        private int textColor;
        // 文字之间的间隔,默认10dp
        private int textPadding;
        // 文字最大放大比例,默认2.0f
        private float textMaxScale;
        // 文字最小alpha值,范围0.0f~1.0f,默认0.4f
        private float textMinAlpha;
        // 是否循环模式,默认是
        private boolean isRecycleMode;
        // 正常状态下最多显示几个文字,默认3(偶数时,边缘的文字会截断)
        private int maxShowNum;
    
        private TextPaint textPaint;
        private Paint.FontMetrics fm;
    
        private Scroller scroller;
        private VelocityTracker velocityTracker;
        private int minimumVelocity;
        private int maximumVelocity;
        private int scaledTouchSlop;
    
        // 数据
        private ArrayList<String> dataList = new ArrayList<>();
        // 中间x坐标
        private int cx;
        // 中间y坐标
        private int cy;
        // 文字最大宽度
        private float maxTextWidth;
        // 文字高度
        private int textHeight;
        // 实际内容宽度
        private int contentWidth;
        // 实际内容高度
        private int contentHeight;
    
        // 按下时的y坐标
        private float downY;
        // 本次滑动的y坐标偏移值
        private float offsetY;
        // 在fling之前的offsetY
        private float oldOffsetY;
        // 当前选中项
        private int curIndex;
        private int offsetIndex;
    
        // 回弹距离
        private float bounceDistance;
        // 是否正处于滑动状态
        private boolean isSliding = false;
    
        public EasyPickerView(Context context) {
            this(context, null);
        }
    
        public EasyPickerView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public EasyPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EasyPickerView, defStyleAttr, 0);
            textSize = a.getDimensionPixelSize(R.styleable.EasyPickerView_epvTextSize, (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
            textColor = a.getColor(R.styleable.EasyPickerView_epvTextColor, Color.BLACK);
            textPadding = a.getDimensionPixelSize(R.styleable.EasyPickerView_epvTextPadding, (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics()));
            textMaxScale = a.getFloat(R.styleable.EasyPickerView_epvTextMaxScale, 2.0f);
            textMinAlpha = a.getFloat(R.styleable.EasyPickerView_epvTextMinAlpha, 0.4f);
            isRecycleMode = a.getBoolean(R.styleable.EasyPickerView_epvRecycleMode, true);
            maxShowNum = a.getInteger(R.styleable.EasyPickerView_epvMaxShowNum, 3);
            a.recycle();
    
            textPaint = new TextPaint();
            textPaint.setColor(textColor);
            textPaint.setTextSize(textSize);
            textPaint.setAntiAlias(true);
            fm = textPaint.getFontMetrics();
    
            scroller = new Scroller(context);
            minimumVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
            maximumVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();
            scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int mode = MeasureSpec.getMode(widthMeasureSpec);
            int width = MeasureSpec.getSize(widthMeasureSpec);
            contentWidth = (int) (maxTextWidth + getPaddingLeft() + getPaddingRight());
            if (mode != MeasureSpec.EXACTLY) { // wrap_content
                width = contentWidth;
            }
    
            mode = MeasureSpec.getMode(heightMeasureSpec);
            int height = MeasureSpec.getSize(heightMeasureSpec);
            textHeight = (int) (fm.bottom - fm.top);
            contentHeight = textHeight * maxShowNum + textPadding * maxShowNum;
            if (mode != MeasureSpec.EXACTLY) { // wrap_content
                height = contentHeight + getPaddingTop() + getPaddingBottom();
            }
    
            cx = width / 2;
            cy = height / 2;
    
            setMeasuredDimension(width, height);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            addVelocityTracker(event);
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if (!scroller.isFinished()) {
                        scroller.forceFinished(true);
                        finishScroll();
                    }
                    downY = event.getY();
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    offsetY = event.getY() - downY;
                    if (isSliding || Math.abs(offsetY) > scaledTouchSlop) {
                        isSliding = true;
                        reDraw();
                    }
                    break;
    
                case MotionEvent.ACTION_UP:
                    int scrollYVelocity = 2 * getScrollYVelocity() / 3;
                    if (Math.abs(scrollYVelocity) > minimumVelocity) {
                        oldOffsetY = offsetY;
                        scroller.fling(0, 0, 0, scrollYVelocity, 0, 0, -Integer.MAX_VALUE, Integer.MAX_VALUE);
                        invalidate();
                    } else {
                        finishScroll();
                    }
    
                    // 没有滑动,则判断点击事件
                    if (!isSliding) {
                        if (downY < contentHeight / 3)
                            moveBy(-1);
                        else if (downY > 2 * contentHeight / 3)
                            moveBy(1);
                    }
    
                    isSliding = false;
                    recycleVelocityTracker();
                    break;
            }
            return true;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            if (null != dataList && dataList.size() > 0) {
                canvas.clipRect(
                        cx - contentWidth / 2,
                        cy - contentHeight / 2,
                        cx + contentWidth / 2,
                        cy + contentHeight / 2
                );
    
                // 绘制文字,从当前中间项往前2个开始,往后一共绘制5个字
                int size = dataList.size();
                int centerPadding = textHeight + textPadding;
                int half = maxShowNum / 2 + 1;
                for (int i = -half; i <= half; i++) {
                    int index = curIndex - offsetIndex + i;
    
                    if (isRecycleMode) {
                        if (index < 0)
                            index = (index + 1) % dataList.size() + dataList.size() - 1;
                        else if (index > dataList.size() - 1)
                            index = index % dataList.size();
                    }
    
                    if (index >= 0 && index < size) {
                        // 计算每个字的中间y坐标
                        int tempY = cy + i * centerPadding;
                        tempY += offsetY % centerPadding;
    
                        // 根据每个字中间y坐标到cy的距离,计算出scale值
                        float scale = 1.0f - (1.0f * Math.abs(tempY - cy) / centerPadding);
    
                        // 根据textMaxScale,计算出tempScale值,即实际text应该放大的倍数,范围 1~textMaxScale
                        float tempScale = scale * (textMaxScale - 1.0f) + 1.0f;
                        tempScale = tempScale < 1.0f ? 1.0f : tempScale;
                        textPaint.setTextSize(textSize * tempScale);
                        textPaint.setAlpha((int) (255 * textMinAlpha * tempScale));
    
                        // 绘制
                        Paint.FontMetrics tempFm = textPaint.getFontMetrics();
                        String text = dataList.get(index);
                        float textWidth = textPaint.measureText(text);
                        canvas.drawText(text, cx - textWidth / 2, tempY - (tempFm.ascent + tempFm.descent) / 2, textPaint);
                    }
                }
            }
        }
    
        @Override
        public void computeScroll() {
            if (scroller.computeScrollOffset()) {
                offsetY = oldOffsetY + scroller.getCurrY();
    
                if (!scroller.isFinished())
                    reDraw();
                else
                    finishScroll();
            }
        }
    
        private void addVelocityTracker(MotionEvent event) {
            if (velocityTracker == null)
                velocityTracker = VelocityTracker.obtain();
    
            velocityTracker.addMovement(event);
        }
    
        private void recycleVelocityTracker() {
            if (velocityTracker != null) {
                velocityTracker.recycle();
                velocityTracker = null;
            }
        }
    
        private int getScrollYVelocity() {
            velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
            int velocity = (int) velocityTracker.getYVelocity();
            return velocity;
        }
    
        private void reDraw() {
            // curIndex需要偏移的量
            int i = (int) (offsetY / (textHeight + textPadding));
            if (isRecycleMode || (curIndex - i >= 0 && curIndex - i < dataList.size())) {
                if (offsetIndex != i) {
                    offsetIndex = i;
    
                    if (null != onScrollChangedListener)
                        onScrollChangedListener.onScrollChanged(getNowIndex(-offsetIndex));
                }
                postInvalidate();
            } else {
                finishScroll();
            }
        }
    
        private void finishScroll() {
            // 判断结束滑动后应该停留在哪个位置
            int centerPadding = textHeight + textPadding;
            float v = offsetY % centerPadding;
            if (v > 0.5f * centerPadding)
                ++offsetIndex;
            else if (v < -0.5f * centerPadding)
                --offsetIndex;
    
            // 重置curIndex
            curIndex = getNowIndex(-offsetIndex);
    
            // 计算回弹的距离
            bounceDistance = offsetIndex * centerPadding - offsetY;
            offsetY += bounceDistance;
    
            // 更新
            if (null != onScrollChangedListener)
                onScrollChangedListener.onScrollFinished(curIndex);
    
            // 重绘
            reset();
            postInvalidate();
        }
    
        private int getNowIndex(int offsetIndex) {
            int index = curIndex + offsetIndex;
            if (isRecycleMode) {
                if (index < 0)
                    index = (index + 1) % dataList.size() + dataList.size() - 1;
                else if (index > dataList.size() - 1)
                    index = index % dataList.size();
            } else {
                if (index < 0)
                    index = 0;
                else if (index > dataList.size() - 1)
                    index = dataList.size() - 1;
            }
            return index;
        }
    
        private void reset() {
            offsetY = 0;
            oldOffsetY = 0;
            offsetIndex = 0;
            bounceDistance = 0;
        }
    
        /**
         * 设置要显示的数据
         *
         * @param dataList 要显示的数据
         */
        public void setDataList(ArrayList<String> dataList) {
            this.dataList.clear();
            this.dataList.addAll(dataList);
    
            // 更新maxTextWidth
            if (null != dataList && dataList.size() > 0) {
                int size = dataList.size();
                for (int i = 0; i < size; i++) {
                    float tempWidth = textPaint.measureText(dataList.get(i));
                    if (tempWidth > maxTextWidth)
                        maxTextWidth = tempWidth;
                }
                curIndex = 0;
            }
            requestLayout();
        }
    
        /**
         * 获取当前状态下,选中的下标
         *
         * @return 选中的下标
         */
        public int getCurIndex() {
            return curIndex - offsetIndex;
        }
    
        /**
         * 滚动到指定位置
         *
         * @param index 需要滚动到的指定位置
         */
        public void moveTo(int index) {
            if (index < 0 || index >= dataList.size() || curIndex == index)
                return;
    
            if (!scroller.isFinished())
                scroller.forceFinished(true);
    
            finishScroll();
    
            int dy = 0;
            int centerPadding = textHeight + textPadding;
            if (!isRecycleMode) {
                dy = (curIndex - index) * centerPadding;
            } else {
                int offsetIndex = curIndex - index;
                int d1 = Math.abs(offsetIndex) * centerPadding;
                int d2 = (dataList.size() - Math.abs(offsetIndex)) * centerPadding;
    
                if (offsetIndex > 0) {
                    if (d1 < d2)
                        dy = d1; // ascent
                    else
                        dy = -d2; // descent
                } else {
                    if (d1 < d2)
                        dy = -d1; // descent
                    else
                        dy = d2; // ascent
                }
            }
            scroller.startScroll(0, 0, 0, dy, 500);
            invalidate();
        }
    
        /**
         * 滚动指定的偏移量
         *
         * @param offsetIndex 指定的偏移量
         */
        public void moveBy(int offsetIndex) {
            moveTo(getNowIndex(offsetIndex));
        }
    
        /**
         * 滚动发生变化时的回调接口
         */
        public interface OnScrollChangedListener {
            public void onScrollChanged(int curIndex);
    
            public void onScrollFinished(int curIndex);
        }
    
        private OnScrollChangedListener onScrollChangedListener;
    
        public void setOnScrollChangedListener(OnScrollChangedListener onScrollChangedListener) {
            this.onScrollChangedListener = onScrollChangedListener;
        }
    }
    

     MainActivity.java

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    import java.util.ArrayList;
    
    public class MainActivity extends AppCompatActivity {
    
        private int hour;
        private int minute;
        private TextView tv;
        private EasyPickerView epvH;
        private EasyPickerView epvM;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            tv = (TextView) findViewById(R.id.tv);
    
            // hours
            initHours();
    
            // minutes
            initMinutes();
    
            // btns
            initBtns();
        }
    
        private void initHours() {
            epvH = (EasyPickerView) findViewById(R.id.epv_h);
            final ArrayList<String> hDataList = new ArrayList<>();
            for (int i = 0; i < 24; i++)
                hDataList.add("" + i);
    
            epvH.setDataList(hDataList);
            epvH.setOnScrollChangedListener(new EasyPickerView.OnScrollChangedListener() {
                @Override
                public void onScrollChanged(int curIndex) {
                    hour = Integer.parseInt(hDataList.get(curIndex));
                    tv.setText(hour + "h" + minute + "m");
                }
    
                @Override
                public void onScrollFinished(int curIndex) {
                    hour = Integer.parseInt(hDataList.get(curIndex));
                    tv.setText(hour + "h" + minute + "m");
                }
            });
        }
    
        private void initMinutes() {
            epvM = (EasyPickerView) findViewById(R.id.epv_m);
            final ArrayList<String> dataList2 = new ArrayList<>();
            for (int i = 0; i < 60; i++)
                dataList2.add("" + i);
    
            epvM.setDataList(dataList2);
            epvM.setOnScrollChangedListener(new EasyPickerView.OnScrollChangedListener() {
                @Override
                public void onScrollChanged(int curIndex) {
                    minute = Integer.parseInt(dataList2.get(curIndex));
                    tv.setText(hour + "h" + minute + "m");
                }
    
                @Override
                public void onScrollFinished(int curIndex) {
                    minute = Integer.parseInt(dataList2.get(curIndex));
                    tv.setText(hour + "h" + minute + "m");
                }
            });
        }
    
        private void initBtns() {
            // hours
            final EditText et_h = (EditText) findViewById(R.id.et_h);
    
            Button btnTo = (Button) findViewById(R.id.btn_to_h);
            btnTo.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!TextUtils.isEmpty(et_h.getText())) {
                        int index = Integer.parseInt(et_h.getText().toString());
                        epvH.moveTo(index);
                    }
                }
            });
    
            Button btnBy = (Button) findViewById(R.id.btn_by_h);
            btnBy.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!TextUtils.isEmpty(et_h.getText())) {
                        int index = Integer.parseInt(et_h.getText().toString());
                        epvH.moveBy(index);
                    }
                }
            });
    
            // minutes
            final EditText et_m = (EditText) findViewById(R.id.et_m);
    
            Button btnTo_m = (Button) findViewById(R.id.btn_to_m);
            btnTo_m.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!TextUtils.isEmpty(et_m.getText())) {
                        int index = Integer.parseInt(et_m.getText().toString());
                        epvM.moveTo(index);
                    }
                }
            });
    
            Button btnBy_m = (Button) findViewById(R.id.btn_by_m);
            btnBy_m.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!TextUtils.isEmpty(et_m.getText())) {
                        int index = Integer.parseInt(et_m.getText().toString());
                        epvM.moveBy(index);
                    }
                }
            });
        }
    }
    

     activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:custom="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.hzn.easypickerview.MainActivity"
        >
    
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="30dp"
            android:textColor="#8f8f8f"
            android:textSize="20sp"
            />
    
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            >
    
            <com.hzn.easypickerview.EasyPickerView
                android:id="@+id/epv_h"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingLeft="15dp"
                android:paddingRight="15dp"
                custom:epvMaxShowNum="3"
                custom:epvRecycleMode="true"
                custom:epvTextColor="#4aa4a4"
                custom:epvTextMaxScale="2.0"
                custom:epvTextMinAlpha="0.35"
                custom:epvTextPadding="10dp"
                custom:epvTextSize="30sp"
                />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginTop="70dp"
                android:text="h"
                android:textColor="#4aa4a4"
                android:textSize="20sp"
                />
    
            <com.hzn.easypickerview.EasyPickerView
                android:id="@+id/epv_m"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:paddingLeft="15dp"
                android:paddingRight="15dp"
                custom:epvMaxShowNum="3"
                custom:epvRecycleMode="false"
                custom:epvTextColor="#e98d3c"
                custom:epvTextMaxScale="2.0"
                custom:epvTextMinAlpha="0.35"
                custom:epvTextPadding="10dp"
                custom:epvTextSize="30sp"
                />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginTop="70dp"
                android:text="m"
                android:textColor="#e98d3c"
                android:textSize="20sp"
                />
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:gravity="center"
            android:orientation="horizontal"
            >
    
            <EditText
                android:id="@+id/et_h"
                android:layout_width="60dp"
                android:layout_height="40dp"
                android:hint="h"
                android:inputType="numberSigned"
                android:textColor="#4aa4a4"
                android:textColorHint="#dedede"
                />
    
            <Button
                android:id="@+id/btn_to_h"
                android:layout_width="wrap_content"
                android:layout_height="30dp"
                android:layout_marginLeft="10dp"
                android:background="#4aa4a4"
                android:text="move to"
                android:textColor="#ffffff"
                />
    
            <Button
                android:id="@+id/btn_by_h"
                android:layout_width="wrap_content"
                android:layout_height="30dp"
                android:layout_marginLeft="10dp"
                android:background="#4aa4a4"
                android:minHeight="0dp"
                android:text="move by"
                android:textColor="#ffffff"
                />
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:gravity="center"
            android:orientation="horizontal"
            >
    
            <EditText
                android:id="@+id/et_m"
                android:layout_width="60dp"
                android:layout_height="40dp"
                android:hint="m"
                android:inputType="numberSigned"
                android:textColor="#4aa4a4"
                android:textColorHint="#dedede"
                />
    
            <Button
                android:id="@+id/btn_to_m"
                android:layout_width="wrap_content"
                android:layout_height="30dp"
                android:layout_marginLeft="10dp"
                android:background="#e98d3c"
                android:text="move to"
                android:textColor="#ffffff"
                />
    
            <Button
                android:id="@+id/btn_by_m"
                android:layout_width="wrap_content"
                android:layout_height="30dp"
                android:layout_marginLeft="10dp"
                android:background="#e98d3c"
                android:text="move by"
                android:textColor="#ffffff"
                />
        </LinearLayout>
    </LinearLayout>
    

     rec.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
           android:shape="rectangle"
        >
    
        <corners android:radius="8dp"/>
        <solid android:color="#f1f1f1"/>
        <stroke
            android:width="1dp"
            android:color="#af1e7f"
            />
    </shape>
    

     atts.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="EasyPickerView">
            <attr name="epvTextSize" format="dimension"/>
            <attr name="epvTextColor" format="color"/>
            <attr name="epvTextPadding" format="dimension"/>
            <attr name="epvTextMaxScale" format="float"/>
            <attr name="epvTextMinAlpha" format="float"/>
            <attr name="epvRecycleMode" format="boolean"/>
            <attr name="epvMaxShowNum" format="integer"/>
        </declare-styleable>
    </resources>
    

     效果图:


    学习来源:http://blog.csdn.net/u012199331/article/details/72778973


  • 相关阅读:
    数字货币资金费策略
    如何利用CCXT交易数字货币合约
    三分钟玩转微软AI量化投资开源库QLib
    商品期货月度效应的统计
    数字货币无风险收益率又双叒叕扩大了!
    Omega System Trading and Development Club内部分享策略Easylanguage源码 (第二期)
    【mac】安装配置 homebrew, Nginx
    【Typescript+Vue】01. easy
    【python】sudo python -m SimpleHTTPServer
    【windows】docker to vmware
  • 原文地址:https://www.cnblogs.com/loaderman/p/6956629.html
Copyright © 2020-2023  润新知