Android Custom WheelView

Keywords: Android xml less Java

function

Wheeled Select View, similar to TimePicker or DataPicker, allows you to set whether or not there is a boundary (the beginning and end meet)

Design sketch

Explain
Inherited from View to assist with scrolling with OverScroller, using a minimum API version of 9.If necessary, you can use Scroller instead, without affecting the effect.
The xml custom property has not been added yet, and only code settings are currently available for style settings.\

Partial Method Description

public void addData(String show,Object backData);   //Add data
public void addData(String data);                   //Add data
public void setCircle(boolean isCircle);            //Set whether the end meets the end
public void setRate(int rate);                      //Set Sliding Speed Change Rate
public void notifyDataSetChanged();                 //Refresh Data and Settings
public void setCenterItem(int position);            //Set the selected location (must be called after data has been added)
public void setCenterItem(String showData);         //Set selected data (must be called after data has been added)
public Object getCenterItem();                      //Get the currently selected data
public void setLineColor(int lineColor);            //Set the color of the middle line
public void setTextColor(int textColor);            //Set the color of the text
public void setTextSize(float textSize);            //Set Text Size```

//Use examples

View wh= LayoutInflater.from(this).inflate(R.layout.common_window_wheel,null);
final WheelView picker= (WheelView) wh.findViewById(R.id.wheel);
Picker.addData(Details);
Picker.addData(Summary);
Picker.addData(medical record);
Picker.addData(doctor's order);
Picker.addData(Test);
Picker.addData(Check);
Picker.addData(Signs);
picker.setCenterItem(4);
WPopupWindow popupWindow=new WPopupWindow(wh);
popupWindow.showAtLocation(getContentView(), Gravity.BOTTOM, 0, 0);
wh.findViewById(R.id.right).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.e("nowData->"+picker.getCenterItem());
}
});

**xml Code**

**Source**

package com.newbjgoodwill.mobilecwr.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;

import java.util.ArrayList;

/**
* Created by zhangjianmin on 2017/12/1.
*/

public class WheelView extends View{
private float scrollY=0;
private int scrollX=0;

private int showSize=5;             //Number of Item s Shown
private float textSize=16;          //Size of text
private boolean isCircle=false;     //Is it a ring
private int rate=120;               //Inertial sliding ratio, the larger rate, the faster rate
private int textColor=0xFF000000;   //Text color
private int lineColor=0xFF888888;   //Line color

private int cacheNowItem=-1;        //Preset the position of the current item, negative number means no setting

private int currentItem=-1;         //Current item location



private int width;                  //Width of view
private int height;                 //Height of view
private int itemHeight;             //Height of item
private int itemX;                  //X position of item
private float centerItemTop;        //Top margin position of center Item
private float centerItemBottom;     //The lower margin position of the central Item
private float centerItemHeight;     //Height of center Item

private int realHeight;             //The actual height of the content
private int minScrollY;             //Minimum Roll Height
private int maxScrollY;             //Maximum Roll Height

private ArrayList<HashBean> data;   //Data Collection
private int dataSize=0;
private Paint paint;
private Paint coverPaint;           //Mask paint
private Shader shader;              //Mask Gradient

private float lastY,downY;          //Coordinates of the last operation and when pressed
private long downTime;              //Time when pressed

private OverScroller mScroller;

public boolean isStart=true;

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

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

public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init(){
    mScroller=new OverScroller(getContext());
    data=new ArrayList<>();
    paint=new Paint();
    paint.setAntiAlias(true);
    paint.setTextSize(DensityUtils.dp2px(getContext(),textSize));
    paint.setTextAlign(Paint.Align.CENTER);
    coverPaint=new Paint();
    if(showSize%2==0){
        showSize+=1;
    }
}

/**
 * Add data
 * @param show  Display data
 * @param backData  Return data when selected
 */
public void addData(String show,Object backData){
    data.add(new HashBean(show, backData));
    dataSize++;
}

/**
 * Add data
 * @param data Display data and return data when selected
 */
public void addDatas(String data,Object showPage){
    addData(data, showPage);
}

public void clearData(){
    data.clear();
}

public void setCircle(boolean isCircle){
    this.isCircle=isCircle;
}

public void setTextColor(int textColor){
    this.textColor=textColor;
    invalidate();
}

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

public void setTextSize(float textSize){
    this.textSize=textSize;
    paint.setTextSize(DensityUtils.dp2px(getContext(),textSize));
    invalidate();
}

public void setRate(int rate){
    this.rate=rate;
}

public void notifyDataSetChanged(){
    isStart=true;
    invalidate();
}

private void measureData(){
    if(isStart){
        width=getWidth();
        itemX=width/2;
        height=getHeight();
        itemHeight=(height-getPaddingTop()-getPaddingBottom())/showSize;
        realHeight=dataSize*itemHeight;
        minScrollY=-(getRealHeight()-(showSize+1)/2*itemHeight);
        maxScrollY=(showSize-1)/2*itemHeight;
        centerItemHeight=itemHeight;
        centerItemTop=(height-getPaddingTop()-getPaddingBottom())/2+getPaddingTop()-centerItemHeight/2;
        centerItemBottom=(height-getPaddingTop()-getPaddingBottom())/2+getPaddingTop()+centerItemHeight/2;
        shader=new LinearGradient(0,0,0,height,new int[]{
                0xFFFFFFFF,0xAAFFFFFF,0x00FFFFFF,0x00FFFFFF,0xAAFFFFFF,0xFFFFFFFF
        },new float[]{
                0.0f,centerItemTop/height,centerItemTop/height,centerItemBottom/height,centerItemBottom/height,1.0f
        }, Shader.TileMode.REPEAT);
        coverPaint.setShader(shader);
        isStart=false;
    }
}

@Override
public void computeScroll() {
    //Is scroller scrolling complete
    if(mScroller.computeScrollOffset()){
        scrollY=mScroller.getCurrY();
        invalidate();
    }
    super.computeScroll();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    measureData();
    //If the current selection is set
    if(cacheNowItem>=0){
        scrollY=-(cacheNowItem-(showSize-1)/2)*itemHeight;
        cacheNowItem=-1;
    }
    int startItemPos=(int)-scrollY/itemHeight;      //Starting position of drawn data
    paint.setColor(textColor);
    for(int i=startItemPos,j=0;i<startItemPos+showSize+2;j++,i++){
        float topY=j*itemHeight+scrollY%itemHeight;
        if(i>=0&&i<dataSize){
            canvas.drawText(data.get(i).showStr,itemX,
                    getBaseLine(paint,topY,itemHeight),paint);
        }else{
            if(isCircle){
                int pos=i%dataSize;
                canvas.drawText(data.get(pos<0?pos+dataSize:pos).showStr,itemX,
                        getBaseLine(paint,topY,itemHeight),paint);
            }
        }
    }

    //Draw middle lines and masks
    paint.setColor(lineColor);
    canvas.drawLine(getPaddingLeft(), centerItemTop, width-getPaddingRight(),centerItemTop,paint);
    canvas.drawLine(getPaddingLeft(), centerItemBottom, width-getPaddingRight(), centerItemBottom, paint);
    coverPaint.setShader(shader);
    canvas.drawRect(0, 0, width, height, coverPaint);
}

/**
 * Get the size of the data collection
 * @param isRefresh Whether to recalculate data collection size
 * @return
 */
public int getDataSize(boolean isRefresh){
    if(isRefresh){
        dataSize=data.size();
    }
    return data.size();
}

/**
 * Set the location of the current Item
 * @param position
 */
public void setCenterItem(int position){
    if(position>=0&&position<dataSize){
        cacheNowItem=position;
    }
    invalidate();
}

/**
 * Set Selection
 * @param showData
 */
public void setCenterItem(String showData){
    int size=data.size();
    for(int i=0;i<size;i++){
        if(showData.equals(data.get(i).showStr)){
            cacheNowItem=i;
            invalidate();
            return;
        }
    }
}

/**
 * Get data for selection
 *
 * @return
 */
public Object getCenterItem(){
    if(cacheNowItem>=0){
        return data.get(cacheNowItem).backData;
    }else{
        int dy=(int)scrollY%itemHeight;         //Less than one Item height
        if(Math.abs(dy)>itemHeight/2){          //If the offset is greater than half of the item,
            if(scrollY<0){
                scrollY= scrollY-itemHeight-dy;
            }else{
                scrollY=scrollY+itemHeight-dy;
            }
        }else{
            scrollY=scrollY-dy;
        }
        mScroller.forceFinished(true);
        invalidate();
        int nowChecked;
        if(!isCircle){
            if(scrollY<minScrollY){
                nowChecked=dataSize-1;
            }else if(scrollY>maxScrollY){
                nowChecked=0;
            }else{
                nowChecked= (int) (-scrollY/itemHeight+(showSize-1)/2);
            }
        }else{
            //When the wheel is on, reset the scrollY position so that it appears at the equivalent position within the bounds
            //Adjust with minScroll as relative 0
            if(scrollY<minScrollY||scrollY>=maxScrollY){
                int mid= (int) ((scrollY-minScrollY)%realHeight);
                if(mid<0){
                    mid+=realHeight;
                }
                scrollY=mid+minScrollY;
            }
            nowChecked= (int) (-scrollY/itemHeight+(showSize-1)/2);
        }
        return dataSize>0?data.get(nowChecked).backData:null;
    }
}
/**
 * Get Selected
 *
 * @return
 */
public Object getCenterData(){
    if(cacheNowItem>=0){
        return data.get(cacheNowItem).showStr;
    }else{
        int dy=(int)scrollY%itemHeight;         //Less than one Item height
        if(Math.abs(dy)>itemHeight/2){          //If the offset is greater than half of the item,
            if(scrollY<0){
                scrollY= scrollY-itemHeight-dy;
            }else{
                scrollY=scrollY+itemHeight-dy;
            }
        }else{
            scrollY=scrollY-dy;
        }
        mScroller.forceFinished(true);
        invalidate();
        int nowChecked;
        if(!isCircle){
            if(scrollY<minScrollY){
                nowChecked=dataSize-1;
            }else if(scrollY>maxScrollY){
                nowChecked=0;
            }else{
                nowChecked= (int) (-scrollY/itemHeight+(showSize-1)/2);
            }
        }else{
            //When the wheel is on, reset the scrollY position so that it appears at the equivalent position within the bounds
            //Adjust with minScroll as relative 0
            if(scrollY<minScrollY||scrollY>=maxScrollY){
                int mid= (int) ((scrollY-minScrollY)%realHeight);
                if(mid<0){
                    mid+=realHeight;
                }
                scrollY=mid+minScrollY;
            }
            nowChecked= (int) (-scrollY/itemHeight+(showSize-1)/2);
        }
        return dataSize>0?data.get(nowChecked).showStr:null;
    }
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            downTime=System.currentTimeMillis();
            downY=event.getRawY();
            lastY=downY;
            break;
        case MotionEvent.ACTION_MOVE:
            float y=event.getRawY();
            float dy=y-lastY;
            pretendScrollY(dy);
            lastY=y;
            break;
        case MotionEvent.ACTION_UP:
            checkStateAndPosition();
            invalidate();
            break;
    }
    return true;
}

private int getRealHeight(){
    if(realHeight==0){
        realHeight=dataSize*itemHeight;
    }
    return realHeight;
}

private void checkStateAndPosition(){
    //Pull up beyond
    if(!isCircle&&scrollY<-(getRealHeight()-(showSize+1)/2*itemHeight)){
        mScroller.startScroll(0, (int)scrollY, 0, (showSize+1)/2*itemHeight-getRealHeight() - (int)scrollY,400);

// mScroller.springBack(0,(int)scrollY,0,0,minScrollY,maxScrollY);
}else if(!IsCircle &&scrollY>(showSize-1)/2*itemHeight){//drop-down exceeds
mScroller.startScroll(0, (int) scrollY, 0, (showSize - 1) / 2 * itemHeight - (int) scrollY, 400);
// mScroller.springBack(0,(int)scrollY,0,0,minScrollY,maxScrollY);
}else{
long endTime=System.currentTimeMillis();
//Out of sliding time or under sliding distance
if(endTime-downTime>250||Math.abs(lastY-downY)

**Source for WPopupWindow**

package com.newbjgoodwill.mobilecwr.view;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.PopupWindow;

/**
* Created by zhangjianmin on 2017/12/1.
*/

public class WPopupWindow extends PopupWindow {
private Context context;
private boolean isBgAlpha=true;
private float alpha=0.5f;

public WPopupWindow(View contentView) {
    this(contentView, ViewGroup.LayoutParams.MATCH_PARENT , ViewGroup.LayoutParams.WRAP_CONTENT);
}

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

public WPopupWindow(int width, int height) {
    this(null,width, height);
}

public WPopupWindow(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}
public WPopupWindow(View contentView, int width, int height) {
    this(contentView, width, height, false);
}
public WPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public WPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    this.context=context;
    init();
}

public WPopupWindow(View contentView, int width, int height, boolean focusable) {
    super(contentView, width, height, focusable);
    this.context=contentView.getContext();
    init();
}

public void setBgAlpha(boolean isAlpha,float alpha){
    this.isBgAlpha=isAlpha;
    this.alpha=alpha;
}

@Override
public void showAsDropDown(View anchor) {
    this.showAsDropDown(anchor,0,0);
}

@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
    this.showAsDropDown(anchor, xoff, yoff, Gravity.TOP | Gravity.START);
}

@Override
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
    setWindowFilter(isBgAlpha, alpha);
    super.showAsDropDown(anchor, xoff, yoff, gravity);
}

@Override
public void showAtLocation(View parent, int gravity, int x, int y) {
    setWindowFilter(isBgAlpha, alpha);
    super.showAtLocation(parent, gravity, x, y);
}

public void init(){
    setOnDismissListener(new OnDismissListener() {
        @Override
        public void onDismiss() {
            setWindowFilter(isBgAlpha, 1f);
        }
    });
    setFocusable(true);
    setTouchable(true);
    setOutsideTouchable(true);
    setOutTouchCancel(false);
}

/**
 * @param isCancel  Whether to cancel the dialog when you click outside it
 */
public void setOutTouchCancel(boolean isCancel){
    if(isCancel){
        setBackgroundDrawable(new BitmapDrawable());
        setTouchInterceptor(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (MotionEvent.ACTION_OUTSIDE == event.getAction()) {
                    dismiss();
                    return true;
                }
                return false;
            }
        });
    }else{
        setBackgroundDrawable(null);
        setTouchInterceptor(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;
            }
        });
    }
}

public void setWindowFilter(boolean isBgAlpha,float alpha) {
    if (isBgAlpha) {
        WindowManager.LayoutParams lp = ((Activity) context).getWindow().getAttributes();
        lp.alpha = alpha;
        //Guarantee that Huawei honor darkens
        lp.dimAmount=alpha;
        ((Activity) context).getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
        ((Activity) context).getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        ////////////////////////////////////////////////////
        ((Activity) context).getWindow().setAttributes(lp);
    }
}

}

**DensityUtils Source**

package com.newbjgoodwill.mobilecwr.view;

import android.content.Context;
import android.util.TypedValue;

/**
* Created by zhangjianmin on 2017/12/1.
*/

public class DensityUtils {
private DensityUtils() {
/* cannot be instantiated */
throw new UnsupportedOperationException("cannot be instantiated");
}

/**
 * dp To px
 *
 * @param context
 * @param val
 * @return
 */
public static float dp2px(Context context, float dpVal) {
    return (float) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
            dpVal, context.getResources().getDisplayMetrics());
}

/**
 * sp To px
 *
 * @param context
 * @param val
 * @return
 */
public static int sp2px(Context context, float spVal) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
            spVal, context.getResources().getDisplayMetrics());
}

/**
 * px To dp
 *
 * @param context
 * @param pxVal
 * @return
 */
public static float px2dp(Context context, float pxVal) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (pxVal / scale);
}

/**
 * px Transfer to sp
 *
 * @param fontScale
 * @param pxVal
 * @return
 */
public static float px2sp(Context context, float pxVal) {
    return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);
}

}

"`

Posted by bobicles2 on Mon, 13 Jul 2020 08:46:00 -0700