您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“怎么用Android實現(xiàn)底部滾輪式選擇彈跳框”,內容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“怎么用Android實現(xiàn)底部滾輪式選擇彈跳框”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
先看效果:
調用方法:
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); //數(shù)據 private int selectPos; //默認選中位置 private OnSelectListener mSelectListener; //監(jiān)聽 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(); } }); //設置數(shù)據 pickerView.setDataList(list); //監(jiān)聽數(shù)據 pickerView.setOnScrollChangedListener(new EasyPickerView.OnScrollChangedListener() { @Override public void onScrollChanged(int curIndex) { //滾動時選中項發(fā)生變化 } @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:是否為循環(huán)模式
- 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; /** * 滾輪視圖,可設置是否循環(huán)模式,實現(xiàn)OnScrollChangedListener接口以監(jiān)聽滾輪變化 */ 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; // 是否循環(huán)模式,默認是 private boolean isRecycleMode; // 正常狀態(tài)下最多顯示幾個文字,默認3(偶數(shù)時,邊緣的文字會截斷) 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; // 數(shù)據 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; // 是否正處于滑動狀態(tài) 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應該放大的倍數(shù),范圍 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; } /** * 設置要顯示的數(shù)據 * * @param dataList 要顯示的數(shù)據 */ 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(); } /** * 獲取當前狀態(tài)下,選中的下標 * * @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)); } /** * 滾動發(fā)生變化時的回調接口 */ 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>
讀到這里,這篇“怎么用Android實現(xiàn)底部滾輪式選擇彈跳框”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。