根据jiangshicheng的仿IOS的wheel view改写的多级联动

Posted yzh315

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了根据jiangshicheng的仿IOS的wheel view改写的多级联动相关的知识,希望对你有一定的参考价值。

先上效果图

 

技术图片

 

下面再上 https://github.com/JustinRoom/WheelViewDemo 中需要用到的几个类

1.  IWheelViewSetting.java
import android.support.annotation.ColorInt;

/**
 * Wheel view without selected mask.
 *
 * <br>Email:1006368252@qq.com
 * <br>QQ:1006368252
 * <br><a href="https://github.com/JustinRoom/WheelViewDemo" target="_blank">https://github.com/JustinRoom/WheelViewDemo</a>
 *
 * @author jiangshicheng
 */
public interface IWheelViewSetting {

    WheelItemBean getSelectWheelItem();

    void setTextSize(float textSize);

    void setTextColor(@ColorInt int textColor);

    void setShowCount(int showCount);

    void setTotalOffsetX(int totalOffsetX);

    void setItemVerticalSpace(int itemVerticalSpace);

    void setItems(WheelItemBean[] items, int initIndex);

    int getSelectedIndex();

    void setSelectedIndex(int targetIndexPosition);

    void setSelectedIndex(int targetIndexPosition, boolean withAnimation);

    void setOnSelectedListener(WheelView.OnSelectedListener onSelectedListener);

    boolean isScrolling();
}

2.   WheelItemView.java

import android.content.Context;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.FrameLayout;


/**
 * Wheel view without selected mask.
 *
 * <br>Email:1006368252@qq.com
 * <br>QQ:1006368252
 * <br><a href="https://github.com/JustinRoom/WheelViewDemo" target="_blank">https://github.com/JustinRoom/WheelViewDemo</a>
 *
 * @author jiangshicheng
 */
public class WheelItemView extends FrameLayout implements IWheelViewSetting {

    private WheelView wheelView;
    private WheelMaskView wheelMaskView;

    public WheelItemView(@NonNull Context context) {
        super(context);
        initAttr(context, null, 0);
    }

    public WheelItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initAttr(context, attrs, 0);
    }

    public WheelItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs, defStyleAttr);
    }

    private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        wheelView = new WheelView(context);
        wheelView.initAttr(context, attrs, defStyleAttr);
        wheelMaskView = new WheelMaskView(context);
        wheelMaskView.initAttr(context, attrs, defStyleAttr);
        addView(wheelView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        addView(wheelMaskView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ViewGroup.LayoutParams params = wheelMaskView.getLayoutParams();
        params.height = wheelView.getMeasuredHeight();
        wheelMaskView.setLayoutParams(params);
        wheelMaskView.updateMask(wheelView.getShowCount(), wheelView.getItemHeight());
    }

    @Override
    public WheelItemBean getSelectWheelItem(){
        try{
            return wheelView.getSelectWheelItem();
        }catch (Exception e){}
        return null;
    }

    public WheelItemBean getIndexWheelItem(int index){
        try{
            return wheelView.getItemIndex(index);
        }catch (Exception e){}
        return null;
    }

    @Override
    public void setTextSize(float textSize) {
        wheelView.setTextSize(textSize);
    }

    @Override
    public void setTextColor(@ColorInt int textColor) {
        wheelView.setTextColor(textColor);
    }

    @Override
    public void setShowCount(int showCount) {
        wheelView.setShowCount(showCount);
    }

    @Override
    public void setTotalOffsetX(int totalOffsetX) {
        wheelView.setTotalOffsetX(totalOffsetX);
    }

    @Override
    public void setItemVerticalSpace(int itemVerticalSpace) {
        wheelView.setItemVerticalSpace(itemVerticalSpace);
    }

    @Override
    public void setItems(WheelItemBean[] items, int initIndex) {
        wheelView.setItems(items,initIndex);
    }

    @Override
    public int getSelectedIndex() {
        return wheelView.getSelectedIndex();
    }

    @Override
    public void setSelectedIndex(int targetIndexPosition) {
        setSelectedIndex(targetIndexPosition, true);
    }

    @Override
    public void setSelectedIndex(int targetIndexPosition, boolean withAnimation) {
        wheelView.setSelectedIndex(targetIndexPosition, withAnimation);
    }

    @Override
    public void setOnSelectedListener(WheelView.OnSelectedListener onSelectedListener) {
        wheelView.setOnSelectedListener(onSelectedListener);
    }

    public void setMaskLineColor(@ColorInt int color) {
        wheelMaskView.setLineColor(color);
    }

    @Override
    public boolean isScrolling() {
        return wheelView.isScrolling();
    }

    public WheelView getWheelView() {
        return wheelView;
    }

    public WheelMaskView getWheelMaskView() {
        return wheelMaskView;
    }
}

 

3.  WheelMaskView.java

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.ColorInt;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;


/**
 * Wheel view without selected mask.
 *
 * <br>Email:1006368252@qq.com
 * <br>QQ:1006368252
 * <br><a href="https://github.com/JustinRoom/WheelViewDemo" target="_blank">https://github.com/JustinRoom/WheelViewDemo</a>
 *
 * @author jiangshicheng
 */
public class WheelMaskView extends View {

    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int top = 0;
    private int bottom = 0;
//    private int lineColor = 0x8F0000FF;
    private int lineColor = Color.GREEN ;

    public WheelMaskView(Context context) {
        super(context);
        initAttr(context, null, 0);
    }

    public WheelMaskView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initAttr(context, attrs, 0);
    }

    public WheelMaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs, defStyleAttr);
    }

    public void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);
    }

    public void updateMask(int heightCount, int itemHeight) {
        if (heightCount > 0) {
            int centerIndex = heightCount / 2;
            top = centerIndex * itemHeight;
            bottom = top + itemHeight;
        } else {
            top = 0;
            bottom = 0;
        }
        invalidate();
    }

    public void setLineColor(@ColorInt int lineColor) {
        this.lineColor = lineColor;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (top > 0 && bottom > 0) {
            paint.setColor(lineColor);
            canvas.drawLine(0, top, getWidth(), top, paint);
            canvas.drawLine(0, bottom, getWidth(), bottom, paint);
        }
    }
}

4. WheelView.java

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
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.view.animation.LinearInterpolator;
import android.widget.OverScroller;


/**
 * Wheel view without selected mask.
 *
 * <br>Email:1006368252@qq.com
 * <br>QQ:1006368252
 * <br><a href="https://github.com/JustinRoom/WheelViewDemo" target="_blank">https://github.com/JustinRoom/WheelViewDemo</a>
 *
 * @author jiangshicheng
 */
public class WheelView extends View implements IWheelViewSetting {

    private static final float DEFAULT_ROTATION_X = 45.0f;
    private static final int DEFAULT_VELOCITY_UNITS = 600;
    private TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    private Camera camera = new Camera();
    private Matrix matrix = new Matrix();
    private float textBaseLine = 0;

    private WheelItemBean[] items = null;

    private int textColor = 0xFF333333;
    /**
     * The size of showing text.
     * Default value is 14dp.
     */
    private float textSize = 0.0f;
    /**
     * The offset pixel from x coordination.
     * <br>text align {@code right} with a positive value
     * <br>text align {@code center} with 0 value
     * <br>text align {@code left} with a negative value
     */
    private int totalOffsetX = 0;
    /**
     * the average pixel length of show text.
     */
    private float averageShowTextLength = 0;
    /**
     * The most showing item count.
     * use it to measure view‘s height
     */
    private int showCount = 5;
    /**
     * The most draw item count.
     */
    private int drawCount = showCount + 2;
    private Rect[] defaultRectArray = null;
    private Rect[] drawRectArray = null;
    private int offsetY = 0;
    private int totalMoveY = 0;//
    private float wheelRotationX = DEFAULT_ROTATION_X ;
    private int velocityUnits = DEFAULT_VELOCITY_UNITS;

    /**
     * the space width of two items
     */
    private int itemVerticalSpace = 32;
    /**
     * the height of every item
     */
    private int itemHeight = 0;
    private float lastX = 0.0f;
    private float lastY = 0.0f;
    private int[] calculateResult = new int[2];//for saving the calculate result.
    private int selectedIndex = 0;//the selected index position
    private OnSelectedListener onSelectedListener = null;
    private ValueAnimator animator = null;
    private boolean isScrolling = false;
    private boolean isAnimatorCanceledForwardly = false;//whether cancel auto scroll animation forwardly

    private static final long CLICK_EVENT_INTERNAL_TIME = 1000;
    private RectF rectF = new RectF();
    private long touchDownTimeStamp = 0;

    //about fling action
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private int scaledTouchSlop;
    private VelocityTracker mVelocityTracker = null;
    private OverScroller mOverScroller;
    private int flingDirection = 0;//-1向上、1向下

    public WheelView(Context context) {
        super(context);
        initAttr(context, null, 0);
    }

    public WheelView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initAttr(context, attrs, 0);
    }

    public WheelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs, defStyleAttr);
    }

    public void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        mOverScroller = new OverScroller(context);
        final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
        scaledTouchSlop = viewConfiguration.getScaledTouchSlop();

        float defaultTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, getResources().getDisplayMetrics());
        textSize = defaultTextSize;
        if (velocityUnits < 0) {
            velocityUnits = Math.abs(velocityUnits);
        }

        initConfig();
        if (isInEditMode()) {
            WheelItemBean[] items = new WheelItemBean[50];
            for (int i = 0; i < items.length; i++) {
                items[i] = new WheelItemBean();
                items[i].setLabel("选项" + (i < 10 ? "0" + i : String.valueOf(i)));
            }
            setItems(items,0);
        }
    }

    private void initConfig() {
        textPaint.setColor(textColor);
        textPaint.setTextSize(textSize);
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();

        String testText = "选项";
        Rect rect = new Rect();
        textPaint.getTextBounds(testText, 0, testText.length(), rect);
        itemHeight = rect.height() + itemVerticalSpace;
        textBaseLine = -itemHeight / 2.0f + (itemHeight - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;

        if (showCount < 5) {
            showCount = 5;
        }
        if (showCount % 2 == 0) {
            showCount++;
        }
        drawCount = showCount + 2;
        defaultRectArray = new Rect[drawCount];
        drawRectArray = new Rect[drawCount];
        for (int i = 0; i < drawCount; i++) {
            defaultRectArray[i] = new Rect();
            drawRectArray[i] = new Rect();
        }
    }

    @Override
    public void setTextSize(float textSize) {
        this.textSize = textSize;
        initConfig();
        requestLayout();
    }

    @Override
    public void setTextColor(@ColorInt int textColor) {
        this.textColor = textColor;
        textPaint.setColor(textColor);
        invalidate();
    }
    @Override
    public WheelItemBean getSelectWheelItem(){
        try{
            return items[selectedIndex];
        }catch (Exception e){}
        return null;
    }

    public WheelItemBean[] getItems(){
        return items;
    }
    public WheelItemBean getItemIndex(int index){
        try{
            if(index>=items.length||index<0) return null;
            return items[index];
        }catch (Exception e){}
        return null;
    }

    @Override
    public void setShowCount(int showCount) {
        this.showCount = showCount;
        initConfig();
        requestLayout();
    }

    @Override
    public void setTotalOffsetX(int totalOffsetX) {
        this.totalOffsetX = totalOffsetX;
        invalidate();
    }

    @Override
    public void setItemVerticalSpace(int itemVerticalSpace) {
        this.itemVerticalSpace = itemVerticalSpace;
        initConfig();
        requestLayout();
    }

    /**
     * Set the fling velocity units.
     * The default value is {@link #DEFAULT_VELOCITY_UNITS}.
     * @param velocityUnits the velocity units
     */
    public void setVelocityUnits(int velocityUnits) {
        this.velocityUnits = Math.abs(velocityUnits);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int top = 0 - itemHeight;
        for (int i = 0; i < drawCount; i++) {
            defaultRectArray[i].set(0, top, 0, top + itemHeight);
            top += itemHeight;
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(itemHeight * showCount, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isEmpty())
            return super.onTouchEvent(event);

        initVelocityTrackerIfNotExists();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //add event into velocity tracker.
                mVelocityTracker.clear();

                //stop fling and reset fling direction
                flingDirection = 0;
                mOverScroller.forceFinished(true);

                if (animator != null && animator.isRunning()) {
                    isAnimatorCanceledForwardly = true;
                    animator.cancel();
                }
                lastX = event.getX();
                lastY = event.getY();

                //Make it as a click event when touch down,
                //and record touch down time stamp.
                touchDownTimeStamp = System.currentTimeMillis();
                break;
            case MotionEvent.ACTION_MOVE:
                //add event into velocity tracker and compute velocity.
                mVelocityTracker.addMovement(event);

                float currentX = event.getX();
                float currentY = event.getY();
                int distance = (int) (currentY - lastY);

                int direction = 0;
                if (distance == 0)
                    break;

                //if moved, cancel click event
                touchDownTimeStamp = 0;
                direction = distance / Math.abs(distance);

                //initialize touch area
                rectF.set(0, 0, getWidth(), getHeight());
                if (rectF.contains(currentX, currentY)) {
                    //inside touch area, execute move event.
                    lastX = currentX;
                    lastY = currentY;
                    updateByTotalMoveY(totalMoveY + distance, direction);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (System.currentTimeMillis() - touchDownTimeStamp <= CLICK_EVENT_INTERNAL_TIME) {
                    //it‘s a click event, do it
                    executeClickEvent(event.getX(), event.getY());
                    break;
                }

                //calculate current velocity
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(velocityUnits, mMaximumVelocity);
                float currentVelocity = velocityTracker.getYVelocity();
                recycleVelocityTracker();

                final int tempFlingDirection = currentVelocity == 0 ? 0 : (currentVelocity < 0 ? -1 : 1);
                if (Math.abs(currentVelocity) >= mMinimumVelocity) {
                    //it‘s a fling event.
                    flingDirection = tempFlingDirection;
                    mOverScroller.fling(
                            0, totalMoveY,
                            0, (int) currentVelocity,
                            0, 0,
                            -(getItemCount() + showCount / 2) * itemHeight, (showCount / 2) * itemHeight,
                            0, 0
                    );
                    invalidate();
                } else {
                    calculateSelectedIndex(totalMoveY, tempFlingDirection);
                    selectedIndex = calculateResult[0];
                    offsetY = calculateResult[1];
                    //execute rebound animation
                    executeAnimation(
                            totalMoveY,
                            0 - selectedIndex * itemHeight
                    );
                }
                break;

        }
        return true;
    }

    public void setOnSelectedListener(OnSelectedListener onSelectedListener) {
        this.onSelectedListener = onSelectedListener;
    }

    public void setItems(WheelItemBean[] items, int initIndex) {
        this.items = items;
        this.selectedIndex = initIndex ;
        totalMoveY = 0 - itemHeight * initIndex;
        offsetY = 0;
        if (!isEmpty()) {
            averageShowTextLength = calAverageShowTextLength();
            invalidate();
        }
    }

    public int getItemHeight() {
        return itemHeight;
    }

    public int getShowCount() {
        return showCount;
    }

    /**
     * Get the current selected index position.
     *
     * @return the current selected index position
     */
    public int getSelectedIndex() {
        return selectedIndex;
    }

    /**
     * Scroll to fixed index position with animation.
     *
     * @param targetIndexPosition the target index position
     */
    public void setSelectedIndex(int targetIndexPosition) {
        setSelectedIndex(targetIndexPosition, true);
    }

    /**
     * Set the 3D rotation.
     *
     * @param wheelRotationX the rotate wheel base x axis
     */
    public void setWheelRotationX(float wheelRotationX) {
        if (this.wheelRotationX != wheelRotationX) {
            this.wheelRotationX = wheelRotationX;
            invalidate();
        }
    }

    /**
     * Scroll to fixed index position.
     *
     * @param targetIndexPosition the target index position
     * @param withAnimation       true, scroll with animation
     */
    public void setSelectedIndex(int targetIndexPosition, boolean withAnimation) {
        if (targetIndexPosition < 0 || targetIndexPosition >= getItemCount())
            throw new IndexOutOfBoundsException("Out of array bounds.");
        if (withAnimation) {
            executeAnimation(totalMoveY, 0 - itemHeight * targetIndexPosition);
        } else {
            totalMoveY = 0 - itemHeight * targetIndexPosition;
            selectedIndex = targetIndexPosition;
            offsetY = 0;
            invalidate();
            if (onSelectedListener != null)
                onSelectedListener.onSelected(getContext(), selectedIndex);
        }
    }

    @Override
    public boolean isScrolling() {
        return isScrolling;
    }

    /**
     * Calculate average pixel length of show text.
     *
     * @return the average pixel length of show text
     */
    private float calAverageShowTextLength() {
        float totalLength = 0;
        String showText = null;
        for (WheelItemBean wheel : items) {
            showText = wheel.getLabel();
            if (showText == null || showText.length() == 0)
                continue;
            totalLength += textPaint.measureText(showText);
        }
        return totalLength / getItemCount();
    }

    /**
     * Execute click event.
     *
     * @return true, valid click event, else invalid.
     */
    private void executeClickEvent(float upX, float upY) {
        boolean isValidTempSelectedIndex = false;
        int tempSelectedIndex = selectedIndex - drawCount / 2;
        for (int i = 0; i < drawCount; i++) {
            rectF.set(drawRectArray[i]);
            if (rectF.contains(upX, upY)) {
                isValidTempSelectedIndex = true;
                break;
            }
            tempSelectedIndex++;
        }
        if (isValidTempSelectedIndex
                && tempSelectedIndex >= 0
                && tempSelectedIndex < getItemCount()) {
            //Move to target selected index
            setSelectedIndex(tempSelectedIndex);
        }
    }

    private int getItemCount() {
        return items == null ? 0 : items.length;
    }

    private WheelItemBean getItemAt(int position) {
        if (isEmpty() || position < 0 || position >= getItemCount())
            return null;
        return items[position];
    }

    private boolean isEmpty() {
        return getItemCount() == 0;
    }

    /**
     * Execute animation.
     */
    private void executeAnimation(int... values) {
        //if it‘s invalid animation, call back immediately.
        if (invalidAnimation(values)) {
            if (onSelectedListener != null)
                onSelectedListener.onSelected(getContext(), selectedIndex);
            return;
        }
        int duration = 0;
        for (int i = 0; i < values.length; i++) {
            if (i > 0) {
                duration += Math.abs(values[i] - values[i - 1]);
            }
        }
        if (duration == 0) {
            if (onSelectedListener != null)
                onSelectedListener.onSelected(getContext(), selectedIndex);
            return;
        }
        createAnimatorIfNecessary();
        if (animator.isRunning()) {
            isAnimatorCanceledForwardly = true;
            animator.cancel();
        }
        animator.setIntValues(values);
        animator.setDuration(calSuitableDuration(duration));
        animator.start();
    }

    private boolean invalidAnimation(int... values) {
        if (values == null || values.length < 2)
            return true;
        int firstValue = values[0];
        for (int value : values) {
            if (firstValue != value)
                return false;
        }
        return true;
    }

    private int calSuitableDuration(int duration) {
        int result = duration;
        while (result > 1200) {
            result = result / 2;
        }
        return result;
    }

    /**
     * Create auto-scroll animation.
     */
    private void createAnimatorIfNecessary() {
        if (animator == null) {
            animator = new ValueAnimator();
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int tempTotalMoveY = (int) animation.getAnimatedValue();
                    updateByTotalMoveY(tempTotalMoveY, 0);
                }
            });
            animator.setInterpolator(new LinearInterpolator());
            animator.addListener(new Animator.AnimatorListener() {

                @Override
                public void onAnimationStart(Animator animation) {
                    isScrolling = true;
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    isScrolling = false;
                    //Cancel animation forwardly.
                    if (isAnimatorCanceledForwardly) {
                        isAnimatorCanceledForwardly = false;
                        return;
                    }

                    if (onSelectedListener != null)
                        onSelectedListener.onSelected(getContext(), selectedIndex);
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
        }
    }

    public int getTotalMoveY() {
        return totalMoveY;
    }

    private void updateByTotalMoveY(final int totalMoveY, int direction) {
        calculateSelectedIndex(totalMoveY, direction);
        this.totalMoveY = totalMoveY;
        this.selectedIndex = calculateResult[0];
        this.offsetY = calculateResult[1];
        invalidate();
    }

    private void calculateSelectedIndex(int totalMoveY, int direction) {
        int selectedIndex = totalMoveY / (0 - itemHeight);
        int rest = totalMoveY % (0 - itemHeight);
        if (direction > 0 && rest != 0) {
            selectedIndex ++;
            rest = itemHeight - Math.abs(rest);
        }
        //move up
        if (direction < 0 && Math.abs(rest) >= itemHeight / 4) {
            selectedIndex++;
        }
        //move down
        if (direction > 0 && Math.abs(rest) >= itemHeight / 4) {
            selectedIndex --;
        }

        selectedIndex = Math.max(selectedIndex, 0);
        selectedIndex = Math.min(selectedIndex, getItemCount() - 1);
        int offsetY = (0 - selectedIndex * itemHeight) - totalMoveY;
        calculateResult[0] = selectedIndex;
        calculateResult[1] = offsetY;
    }

    @Override
    public void computeScroll() {
        if (mOverScroller.computeScrollOffset()) {
            totalMoveY = mOverScroller.getCurrY();
            updateByTotalMoveY(totalMoveY, 0);
            invalidate();
            return;
        }

        if (flingDirection != 0) {
            final int flingDirectionCopy = flingDirection;
            flingDirection = 0;
            calculateSelectedIndex(totalMoveY, flingDirectionCopy);
            selectedIndex = calculateResult[0];
            offsetY = calculateResult[1];
            //execute rebound animation
            executeAnimation(
                    totalMoveY,
                    0 - selectedIndex * itemHeight
            );
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (isEmpty())
            return;

        int tempStartSelectedIndex = selectedIndex - drawCount / 2;
        for (int i = 0; i < drawCount; i++) {
            Rect rect = drawRectArray[i];
            rect.set(defaultRectArray[i]);
            //record touch area for click event
            rect.left = 0;
            rect.right = getWidth();
            if (tempStartSelectedIndex >= 0 && tempStartSelectedIndex < getItemCount()) {
                drawItem(canvas, rect, getItemAt(tempStartSelectedIndex), -offsetY, textPaint);
            }
            tempStartSelectedIndex++;
        }
        computeScroll();
    }

    private void drawItem(Canvas canvas, Rect rect, WheelItemBean item, int offsetY, TextPaint textPaint) {
        String text = item == null ? "" : item.getLabel();
        if (text == null || text.trim().length() == 0)
            return;

        rect.offset(0, offsetY);
        textPaint.setAlpha(calAlpha(rect));
        final int offsetX = totalOffsetX == 0 ? 0 : calOffsetX(totalOffsetX, rect);
        final float w = textPaint.measureText(text);

        float startX = 0;
        if (totalOffsetX > 0) {
            //show text align right
            final float rightAlignPosition = (getWidth() + averageShowTextLength) / 2.0f;
            startX = rightAlignPosition - w + offsetX;
        } else if (totalOffsetX < 0) {
            //show text align left
            final float leftAlignPosition = (getWidth() - averageShowTextLength) / 2.0f;
            startX = leftAlignPosition + offsetX;
        } else {
            //show text align center_horizontal
            startX = (getWidth() - w) / 2.0f + offsetX;
        }
        float centerX = getWidth() / 2.0f;
        float centerY = rect.exactCenterY();
        float baseLine = centerY + textBaseLine;
        matrix.reset();
        camera.save();
        camera.rotateX(calRotationX(rect, wheelRotationX));
        camera.getMatrix(matrix);
        camera.restore();
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
        if (totalOffsetX > 0) {
            float skewX = 0 - calSkewX(rect);
            centerX = (startX + w) / 2.0f;
            matrix.setSkew(skewX, 0, centerX, centerY);
        } else if (totalOffsetX < 0) {
            float skewX = calSkewX(rect);
            centerX = (startX + w) / 2.0f;
            matrix.setSkew(skewX, 0, centerX, centerY);
        }
        canvas.save();
        canvas.concat(matrix);
        canvas.drawText(text, startX, baseLine, textPaint);
        canvas.restore();
    }

    private int calAlpha(Rect rect) {
        int centerY = getHeight() / 2;
        int distance = Math.abs(centerY - rect.centerY());
        int totalDistance = itemHeight * (showCount / 2);
        float alpha = 0.6f * distance / totalDistance;
        return (int) ((1 - alpha) * 0xFF);
    }

    private float calRotationX(Rect rect, float baseRotationX) {
        int centerY = getHeight() / 2;
        int distance = centerY - rect.centerY();
        int totalDistance = itemHeight * (showCount / 2);
        return baseRotationX * distance * 1.0f / totalDistance;
    }

    private float calSkewX(Rect rect) {
        int centerY = getHeight() / 2;
        int distance = centerY - rect.centerY();
        int totalDistance = itemHeight * (showCount / 2);
        return 0.3f * distance / totalDistance;
    }

    private int calOffsetX(int totalOffsetX, Rect rect) {
        int centerY = getHeight() / 2;
        int distance = Math.abs(centerY - rect.centerY());
        int totalDistance = itemHeight * (showCount / 2);
        return totalOffsetX * distance / totalDistance;
    }

    private void initVelocityTrackerIfNotExists() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
    }

    private void recycleVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    public interface OnSelectedListener {
        void onSelected(Context context, int selectedIndex);
    }
}

对于滚轮最重要的就是这个 WheelView.java 

 

5. WheelItemBean.java  此类和原来的有更改

public class WheelItemBean {
    private String id ;
    private String label;
    private WheelItemBean[] wheelItems ;
    ///////////////////////////////////////////
    public String getLabel() {
        return label;
    }
    public void setLabel(String label) {
        this.label = label;
    }
    public WheelItemBean[] getWheelItems() {
        return wheelItems;
    }
    public void setWheelItems(WheelItemBean[] wheelItems) {
        this.wheelItems = wheelItems;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}

6. ColumnWheelDialog.java 入口类

import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TableRow;
import android.widget.TextView;



import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class ColumnWheelDialog extends Dialog {

    private String tvTitle;
    private String tvCancel;
    private OnClickCallBack cancelCallBack = null;
    private String tvOK;
    private OnClickCallBack okCallBack = null;
    //////////////////////////
    private int evel = 0;//层级
    private Map<Integer,WheelItemView> wheelItemViewMap = new HashMap<>();
    private String initLabelVal[] = null;//初始化值


    private boolean isViewInitialized = false;
    private float textSize;
    private int itemVerticalSpace;
    private WheelItemBean[] fWheelItems = null;

    public ColumnWheelDialog(@NonNull Context context) {
        this(context, R.style.WheelDialog);
    }

    private ColumnWheelDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
    }

    public ColumnWheelDialog setData(int evel, WheelItemBean[] wheelItems){
        this.evel = evel;
        this.fWheelItems = wheelItems;
        return this;
    }
    public ColumnWheelDialog setInitLabel(String ... strings){
        initLabelVal = strings;//设置初始化
        return this;
    }
    public ColumnWheelDialog setTitle(String title) {
        tvTitle = title ;
        return this;
    }
    public ColumnWheelDialog setTextSize(float textSize) {
        this.textSize = textSize;
        return this;
    }

    public ColumnWheelDialog setCancelButton(String cancel, OnClickCallBack cancelCallBack) {
        tvCancel = cancel ;
        this.cancelCallBack = cancelCallBack;
        return this;
    }
    public ColumnWheelDialog setOKButton(String ok, OnClickCallBack okCallBack) {
        tvOK = ok ;
        this.okCallBack = okCallBack;
        return this;
    }

    //show 的时候才调用
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        if (getWindow() != null) {
            getWindow().setGravity(Gravity.BOTTOM);
            getWindow().setBackgroundDrawable(null);
            getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT);
        }
        initView();
    }
    public List<WheelItemBean> getSelectWheelItem(){
        List list = new ArrayList();
        for(int i=0;i<wheelItemViewMap.size();i++){
            WheelItemView wheelItemView = wheelItemViewMap.get(i);
            WheelItemBean wheelItem = wheelItemView.getSelectWheelItem();
            list.add(wheelItem);//设置数据
        }
        return list;
    }
    private void addText(ViewGroup viewGroup,String text,int gravity,int color,View.OnClickListener onClickListener){
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        layoutParams.gravity = gravity ;
        TextView textView = new TextView(viewGroup.getContext());
        textView.setText(text);
        textView.setTextColor(color);
        textView.setTextSize(18);
        textView.setPadding(12,8,12,8);
        if(onClickListener!=null){
            textView.setOnClickListener(onClickListener);//添加事件
        }
        viewGroup.addView(textView,layoutParams);
    }

    /**
     * 获取初始化数据
     * @param theEvel
     * @return
     */
    private int getInitIndex(int theEvel, WheelItemBean[] wheelItems){
        try{
            String initLabelStr = initLabelVal[theEvel];//初始化
            for(int i=0;i<wheelItems.length;i++){
                if(initLabelStr.equals(wheelItems[i].getLabel())){
                    return i ;//返回Index
                }
            }
        }catch (Exception e){}
        return 0 ;//返回0
    }

    private void initView(){
        isViewInitialized = true;
        LinearLayout rootView = new LinearLayout(getContext());//设置
        rootView.setBackgroundColor(Color.WHITE);
        rootView.setOrientation(LinearLayout.VERTICAL);
        ///////////////////////////////////////////////////
        FrameLayout frameLayout = new FrameLayout(getContext());//设置
        frameLayout.setBackgroundColor(Color.parseColor("#F5F5F5"));//设置灰色
        rootView.addView(frameLayout, TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT);
        ///////////////////////////////
        addText(frameLayout, this.tvCancel, Gravity.LEFT, Color.RED, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (cancelCallBack == null) {
                    dismiss();
                    return;
                }
                cancelCallBack.callBack(v,getSelectWheelItem());
                dismiss();
            }
        });
        addText(frameLayout, this.tvOK, Gravity.RIGHT, Color.parseColor("#7CCD7C"), new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (okCallBack == null) {
                    dismiss();
                    return;
                }
                if (isScrolling()) {
                    //正在滚动
                    return;
                }
                okCallBack.callBack(v,getSelectWheelItem()) ;
                dismiss();
            }
        });
        addText(frameLayout,this.tvTitle,Gravity.CENTER,Color.BLACK,null);
        //////////////////////////////////
        LinearLayout contentLine = new LinearLayout(getContext());
        rootView.addView(contentLine,LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);

        setContentView(rootView);
        //////////////////////////////////////////////////////////////////////////
        for(int i=0;i<evel;i++) {
            WheelItemView wheelItemView0 = new WheelItemView(contentLine.getContext());
            if(i==0){
                wheelItemView0.setItems(fWheelItems,getInitIndex(i,fWheelItems));//设置数据
            }else{
                WheelItemBean wheelItem = wheelItemViewMap.get(i-1).getSelectWheelItem();
                if(wheelItem!=null){
                    wheelItemView0.setItems(wheelItem.getWheelItems(),getInitIndex(i,wheelItem.getWheelItems()));
                }
            }
            contentLine.addView(wheelItemView0, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1));
            if (textSize > 0) {
                wheelItemView0.setTextSize(textSize);
            }
            if (itemVerticalSpace > 0) {
                wheelItemView0.setItemVerticalSpace(itemVerticalSpace);
            }
            int nextId = i+1;
            wheelItemView0.setOnSelectedListener(new WheelView.OnSelectedListener() {
                @Override
                public void onSelected(Context context, int selectedIndex) {
                    //选择
                    if(fWheelItems!=null){
                        setDatas(nextId,fWheelItems[selectedIndex].getWheelItems());
                    }
                }
            });
            wheelItemViewMap.put(i,wheelItemView0);//添加
        }

    }
    @Override
    public void show() {
        super.show();
        if (getWindow() != null) {
            getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    }

    public void setItemVerticalSpace(int itemVerticalSpace) {
        this.itemVerticalSpace = itemVerticalSpace;
    }


    public void setDatas(int index, WheelItemBean[] wheelItems){
        if(index<wheelItemViewMap.size()){
            updateShowPicker(wheelItemViewMap.get(index),wheelItems);
//            updateOffsetX(4);
        }
    }

    private boolean isScrolling() {
        boolean reBool = isScrolling(wheelItemViewMap.get(0));//End false
        for(int i=1;i<wheelItemViewMap.size();i++){
            reBool = reBool || isScrolling(wheelItemViewMap.get(i));
        }
        return reBool;
    }

    private void updateShowPicker(WheelItemView wheelItemView, WheelItemBean[] items) {
        boolean hide = (items == null || items.length == 0);
        wheelItemView.setVisibility(hide ? View.GONE : View.VISIBLE);
        if(!hide) {
            wheelItemView.setItems(items, 0);
        }
    }


    private boolean isScrolling(WheelItemView view) {
        return view.isShown() && view.isScrolling();
    }

    public interface OnClickCallBack {
        void callBack(View v,List<WheelItemBean> selectList);
    }
}

里面用到了style 。  this(context, R.style.WheelDialog); 

在styles.xml 中添加如下内容

<style name="WheelDialog" parent="android:style/Theme.Dialog">
        <item name="android:windowFrame">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowBackground">@null</item>
    </style>

 

调用如下

WheelItemBean wheelItems[] = null;
            wheelItems = new WheelItemBean[6];
            for(int i=0;i<6;i++){
                wheelItems[i] = new WheelItemBean();
                wheelItems[i].setLabel("第一层"+i);
                WheelItemBean secondWheels[] = new WheelItemBean[6];
                for(int j=0;j<6;j++){
                    secondWheels[j] = new WheelItemBean();
                    secondWheels[j].setLabel("第二层:"+i+","+j);
                    ///////////////////////
                    WheelItemBean thWheels[] = new WheelItemBean[6];
                    for(int k=0;k<thWheels.length;k++){
                        thWheels[k] = new WheelItemBean();
                        thWheels[k].setLabel("第三层:"+i+","+j+","+k);
                    }
                    secondWheels[j].setWheelItems(thWheels);
                }
                wheelItems[i].setWheelItems(secondWheels);
            }
            ColumnWheelDialog dialog = new ColumnWheelDialog(UserInfoActivity.this);
            dialog.setData(3,wheelItems) //evel 表示几层,wheelItems 级层关系数据
                    .setTitle("选择")
                    .setInitLabel("第一层3","第二层:3,3") //初始化数据
                    .setCancelButton("取消", null)
                    .setOKButton("确定",new com.szjpsj.collegeex.view.wheel.ColumnWheelDialog.OnClickCallBack(){
                        @Override
                        public void callBack(View v, List<WheelItemBean> selectList) {
                            try{
                                for(int i=0;i<selectList.size();i++){
                                    System.out.println("第"+(i+1)+"层选择内容:"+selectList.get(i).getLabel());//如果需要id 可以设置id,此处选择id
                                }
                            }catch (Exception e){}
                        }
                    })
                    .show();

 

以上是关于根据jiangshicheng的仿IOS的wheel view改写的多级联动的主要内容,如果未能解决你的问题,请参考以下文章

[Python] Simple Decorators

java毕设项目开源啦,springboot+Thymeleaf的仿豆瓣电影论坛系统

java毕设项目开源啦,springboot+Thymeleaf的仿豆瓣电影论坛系统

java毕设项目开源啦,springboot+Thymeleaf的仿豆瓣电影论坛系统

.NET6+Avalonia开发支持跨平台的仿WPF应用程序以及基于ubuntu系统的演示

为啥 C++11 不能将不可复制的仿函数移动到 std::function?