Android实现底部滚轮式选择弹跳框

本文实例为大家分享了Android实现底部滚轮式选择弹跳框的具体代码,供大家参考,具体内容如下

先看效果:

调用方法:

SlideDialog slideDialog = new SlideDialog(this, list, false, false); slideDialog.setOnSelectClickListener(new SlideDialog.OnSelectListener() {       @Override       public void onCancel() {           Toast.makeText(GroupFormListActivity.this, "未选择", Toast.LENGTH_SHORT).show();             }       @Override       public void onAgree(String txt) {           Toast.makeText(GroupFormListActivity.this, "已选中", Toast.LENGTH_SHORT).show();             }         }); slideDialog.show();

自定义SlideDialog

package xxx.xxx.xxx.xxx; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.TextView; import androidx.annotation.NonNull; import com.txh.yunyao.R; import com.txh.yunyao.common.views.EasyPickerView; import java.util.ArrayList; import java.util.List; /**  * 底部滑动选择弹跳框  */ public class SlideDialog extends Dialog {     private boolean isCancelable = false;     private boolean isBackCancelable = false;     private Context mContext;   //上下文     private List<String> list = new ArrayList<>(0); //数据     private int selectPos; //默认选中位置     private OnSelectListener mSelectListener; //监听     public SlideDialog(@NonNull Context context, int view, List<String> list, boolean isCancelable, boolean isBackCancelable) {         super(context, R.style.SlideDialog);         this.mContext = context;         this.isCancelable = isCancelable;         this.isBackCancelable = isBackCancelable;         this.list = list;         this.selectPos = 0;     }     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         //设置View         setContentView(R.layout.select_slide_template);         //设置点击物理返回键是否可关闭弹框         setCancelable(isCancelable);         //设置点击弹框外是否可关闭弹框         setCanceledOnTouchOutside(isBackCancelable);         //设置view显示位置         Window window = this.getWindow();         window.setGravity(Gravity.BOTTOM);         WindowManager.LayoutParams params = window.getAttributes();         params.width = WindowManager.LayoutParams.MATCH_PARENT;         params.height = WindowManager.LayoutParams.WRAP_CONTENT;         window.setAttributes(params);         //初始化控件         TextView tv_cancel = findViewById(R.id.tv_cancel);         TextView tv_agree = findViewById(R.id.tv_agree);         EasyPickerView pickerView = findViewById(R.id.pickerView);         //取消点击事件         tv_cancel.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 //取消                 mSelectListener.onCancel();                 dismiss();             }         });         //确认点击事件         tv_agree.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 //确认                 mSelectListener.onAgree(list.get(selectPos));                 dismiss();             }         });         //设置数据         pickerView.setDataList(list);         //监听数据         pickerView.setOnScrollChangedListener(new EasyPickerView.OnScrollChangedListener() {             @Override             public void onScrollChanged(int curIndex) {                 //滚动时选中项发生变化             }             @Override             public void onScrollFinished(int curIndex) {                 //滚动结束                 selectPos = curIndex;             }         });     }     public interface OnSelectListener {         //取消         void onCancel();         //确认         void onAgree(String txt);     }     public void setOnSelectClickListener(OnSelectListener listener) {         this.mSelectListener = listener;     } }

R.style.SlideDialog 

<style name="SlideDialog" parent="@android:style/Theme.Holo.Dialog">         <!-- 是否有边框 -->         <item name="android:windowFrame">@null</item>         <!--是否在悬浮Activity之上  -->         <item name="android:windowIsFloating">true</item>         <!-- 标题 -->         <item name="android:windowNoTitle">true</item>         <!--阴影  -->         <item name="android:windowIsTranslucent">true</item><!--半透明-->         <!--背景透明-->         <item name="android:windowBackground">@android:color/transparent</item>         <!-- 还可以加入一些弹出和退出的动画 (lan)--> </style>

R.layout.select_slide_template

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="wrap_content"     xmlns:app="http://schemas.android.com/apk/res-auto"     android:background="@color/white"     android:orientation="vertical">     <RelativeLayout         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:layout_marginLeft="@dimen/dp_10"         android:layout_marginRight="@dimen/dp_10"         android:paddingTop="@dimen/dp_10"         android:paddingLeft="@dimen/dp_15"         android:paddingRight="@dimen/dp_15">         <TextView             android:id="@+id/tv_cancel"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:text="@string/picture_cancel"             android:padding="3dp"             android:textColor="@color/black" />         <TextView             android:id="@+id/tv_agree"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:layout_alignParentRight="true"             android:text="@string/picture_confirm"             android:padding="3dp"             android:textColor="@color/black" />     </RelativeLayout>     <com.txh.yunyao.common.views.EasyPickerView         android:id="@+id/pickerView"         android:layout_width="match_parent"         android:layout_height="wrap_content"         app:epvMaxShowNum="3"         android:layout_marginBottom="@dimen/dp_15"         android:layout_marginTop="@dimen/dp_15"         app:epvTextColor="@color/black"         app:epvTextPadding="@dimen/dp_10"         app:epvRecycleMode="true"         app:epvTextSize="14dp"/> </LinearLayout>

自定义EasyPickerView支持以下几个属性:

- epvTextSize:字符的大小
- epvTextColor:字符的颜色
- epvTextPadding:字符的间距
- epvTextMaxScale:中间字符缩放的最大值
- epvTextMinAlpha:两端字符最小alpha值
- epvRecycleMode:是否为循环模式
- epvMaxShowNum:显示多少个字符

自定义EasyPickerView

package xxx.xxx.xxx.xxx.xxx; 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 com.txh.yunyao.R; import java.util.ArrayList; import java.util.List; /**  * 滚轮视图,可设置是否循环模式,实现OnScrollChangedListener接口以监听滚轮变化  */ 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 List<String> dataList = new ArrayList<>(0);     // 中间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();         textHeight = (int) (fm.bottom - fm.top);         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 * textMaxScale + getPaddingLeft() + getPaddingRight());         if (mode != MeasureSpec.EXACTLY) { // wrap_content             width = contentWidth;         }         mode = MeasureSpec.getMode(heightMeasureSpec);         int height = MeasureSpec.getSize(heightMeasureSpec);         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 dispatchTouchEvent(MotionEvent event) {         getParent().requestDisallowInterceptTouchEvent(true);         return super.dispatchTouchEvent(event);     }     @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             );             // 绘制文字,从当前中间项往前、后一共绘制maxShowNum个字             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;                     // 计算文字alpha值                     float textAlpha = textMinAlpha;                     if (textMaxScale != 1) {                         float tempAlpha = (tempScale - 1) / (textMaxScale - 1);                         textAlpha = (1 - textMinAlpha) * tempAlpha + textMinAlpha;                     }                     textPaint.setTextSize(textSize * tempScale);                     textPaint.setAlpha((int) (255 * textAlpha));                     // 绘制                     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(List<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();         invalidate();     }     /**      * 获取当前状态下,选中的下标      *      * @return 选中的下标      */     public int getCurIndex() {         return getNowIndex(-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;     } }

attrs中 EasyPickerView配置

<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>

推荐阅读