編輯:關於Android編程
滾輪選擇控件
Android自帶的選擇時間控件有點丑,往往產品和設計都比較嫌棄,希望做成ios一樣的滾輪選擇,下面是我在NumberPicker的基礎上自定義的選擇控件,效果如下:
原理
實現滾輪效果有github上mark比較多的WheelView,但是閱讀源碼發現數據是一次性填入的,選擇時間的話,填入10年就是10*365=3650條數據,也就是new出三千多個TextView,想想都覺得恐怖,肯定是不行的,於是便想到用NumberPicker,動態填充數據,一次只設置5個數據,當選中變化時,重新設置數據填充,所以關鍵在於填充的數據的計算。
設置數據部分邏輯代碼:
/** * 更新左側控件 * 日期:選擇年控件 * 時間:選擇月份和日期控件 * * @param timeMillis */ private void updateLeftValue(long timeMillis) { SimpleDateFormat sdf; String str[] = new String[DATA_SIZE]; if (mCurrentType == TYPE_PICK_DATE) { sdf = new SimpleDateFormat("yyyy"); for (int i = 0; i < DATA_SIZE; i++) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(timeMillis); cal.add(Calendar.YEAR, (i - DATA_SIZE / 2)); str[i] = sdf.format(cal.getTimeInMillis()); } } else { sdf = new SimpleDateFormat("MM-dd EEE"); for (int i = 0; i < DATA_SIZE; i++) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(timeMillis); cal.add(Calendar.DAY_OF_MONTH, (i - DATA_SIZE / 2)); str[i] = sdf.format(cal.getTimeInMillis()); } } mNpLeft.setDisplayedValues(str); mNpLeft.setValue(DATA_SIZE / 2); mNpLeft.postInvalidate(); }
對滾輪的監聽,並重新設置填充數據:
@Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(mTimeMillis); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); int hour = calendar.get(Calendar.HOUR_OF_DAY); int offset = newVal - oldVal; if (picker == mNpLeft) { if (mCurrentType == TYPE_PICK_DATE) { calendar.add(Calendar.YEAR, offset); } else { calendar.add(Calendar.DAY_OF_MONTH, offset); } updateLeftValue(calendar.getTimeInMillis()); mTimeMillis = calendar.getTimeInMillis(); } else if (picker == mNpMiddle) { if (mCurrentType == TYPE_PICK_DATE) { calendar.add(Calendar.MONTH, offset); if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } } else { calendar.add(Calendar.HOUR_OF_DAY, offset); if (calendar.get(Calendar.DAY_OF_MONTH) != day) { calendar.set(Calendar.DAY_OF_MONTH, day); } if (calendar.get(Calendar.MONTH) != month) { calendar.set(Calendar.MONTH, month); } if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } } updateMiddleValue(calendar.getTimeInMillis()); updateRightValue(calendar.getTimeInMillis()); mTimeMillis = calendar.getTimeInMillis(); } else if (picker == mNpRight) { if (mCurrentType == TYPE_PICK_DATE) { int days = getMaxDayOfMonth(year, month + 1); if(day == 1 && offset < 0){ calendar.set(Calendar.DAY_OF_MONTH,days); }else if(day == days && offset > 0){ calendar.set(Calendar.DAY_OF_MONTH,1); }else{ calendar.add(Calendar.DAY_OF_MONTH, offset); } if (calendar.get(Calendar.MONTH) != month) { calendar.set(Calendar.MONTH, month); } if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } Log.e(TAG,"time:::"+test.format(calendar.getTimeInMillis())); } else { calendar.add(Calendar.MINUTE, offset); if (calendar.get(Calendar.HOUR_OF_DAY) != hour) { calendar.set(Calendar.HOUR_OF_DAY, hour); } if (calendar.get(Calendar.DAY_OF_MONTH) != day) { calendar.set(Calendar.DAY_OF_MONTH, day); } if (calendar.get(Calendar.MONTH) != month) { calendar.set(Calendar.MONTH, month); } if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } } updateRightValue(calendar.getTimeInMillis()); mTimeMillis = calendar.getTimeInMillis(); } /** * 向外部發送當前選中時間 */ if (mOnSelectedChangeListener != null) { mOnSelectedChangeListener.onSelected(this,mTimeMillis); } Log.e(TAG, "selected time:" + test.format(mTimeMillis)); }
選擇數值和字符串
同樣的,使用NumberPicker進行封裝,動態填充數值從而實現滾動變換的效果。
完整代碼如下:
package com.example.moore.picktimeview.widget; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.NumberPicker; import android.widget.TextView; /** * Created by Moore on 2016/10/21. */ public class PickValueView extends LinearLayout implements NumberPicker.OnValueChangeListener { private Context mContext; /** * 組件 標題、單位、滾輪 */ private TextView mTitleLeft, mTitleMiddle, mTitleRight; private TextView mUnitLeft, mUnitMiddle, mUnitRight; private MyNumberPicker mNpLeft, mNpMiddle, mNpRight; /** * 數據個數 1列 or 2列 or 3列 */ private int mViewCount = 1; /** * 一組數據長度 */ private final int DATA_SIZE = 3; /** * 需要設置的值與默認值 */ private Object[] mLeftValues; private Object[] mMiddleValues; private Object[] mRightValues; private Object mDefaultLeftValue; private Object mDefaultMiddleValue; private Object mDefaultRightValue; /** * 當前正在顯示的值 */ private Object[] mShowingLeft = new Object[DATA_SIZE]; private Object[] mShowingMiddle = new Object[DATA_SIZE]; private Object[] mShowingRight = new Object[DATA_SIZE]; /** * 步長 */ private int mLeftStep = 5; private int mMiddleStep = 1; private int mRightStep = 1; /** * 回調接口對象 */ private onSelectedChangeListener mSelectedChangeListener; public PickValueView(Context context) { super(context); this.mContext = context; generateView(); } public PickValueView(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; generateView(); } public PickValueView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; generateView(); } /** * 生成視圖 */ private void generateView() { //標題 LinearLayout titleLayout = new LinearLayout(mContext); LayoutParams titleParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); titleParams.setMargins(0, 0, 0, dip2px(12)); titleLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); titleLayout.setOrientation(HORIZONTAL); mTitleLeft = new TextView(mContext); mTitleMiddle = new TextView(mContext); mTitleRight = new TextView(mContext); LayoutParams params = new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1); TextView[] titles = new TextView[]{mTitleLeft, mTitleMiddle, mTitleRight}; for (int i = 0; i < titles.length; i++) { titles[i].setLayoutParams(params); titles[i].setGravity(Gravity.CENTER); titles[i].setTextColor(Color.parseColor("#3434EE")); } titleLayout.addView(mTitleLeft); titleLayout.addView(mTitleMiddle); titleLayout.addView(mTitleRight); //內容 LinearLayout contentLayout = new LinearLayout(mContext); contentLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentLayout.setOrientation(HORIZONTAL); contentLayout.setGravity(Gravity.CENTER); mNpLeft = new MyNumberPicker(mContext); mNpMiddle = new MyNumberPicker(mContext); mNpRight = new MyNumberPicker(mContext); mUnitLeft = new TextView(mContext); mUnitMiddle = new TextView(mContext); mUnitRight = new TextView(mContext); MyNumberPicker[] nps = new MyNumberPicker[]{mNpLeft, mNpMiddle, mNpRight}; for (int i = 0; i < nps.length; i++) { nps[i].setLayoutParams(params); nps[i].setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); nps[i].setOnValueChangedListener(this); } contentLayout.addView(mNpLeft); contentLayout.addView(mUnitLeft); contentLayout.addView(mNpMiddle); contentLayout.addView(mUnitMiddle); contentLayout.addView(mNpRight); contentLayout.addView(mUnitRight); this.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); this.setOrientation(VERTICAL); this.addView(titleLayout); this.addView(contentLayout); } /** * 初始化數據和值 */ private void initViewAndPicker() { if (mViewCount == 1) { this.mNpMiddle.setVisibility(GONE); this.mNpRight.setVisibility(GONE); this.mUnitMiddle.setVisibility(GONE); this.mUnitRight.setVisibility(GONE); } else if (mViewCount == 2) { this.mNpRight.setVisibility(GONE); this.mUnitRight.setVisibility(GONE); } //初始化數組值 if (mLeftValues != null && mLeftValues.length != 0) { if (mLeftValues.length < DATA_SIZE) { for (int i = 0; i < mLeftValues.length; i++) { mShowingLeft[i] = mLeftValues[i]; } for (int i = mLeftValues.length; i < DATA_SIZE; i++) { mShowingLeft[i] = -9999; } } else { for (int i = 0; i < DATA_SIZE; i++) { mShowingLeft[i] = mLeftValues[i]; } } mNpLeft.setMinValue(0); mNpLeft.setMaxValue(DATA_SIZE - 1); if (mDefaultLeftValue != null) updateLeftView(mDefaultLeftValue); else updateLeftView(mShowingLeft[0]); } /** * 中間控件 */ if (mViewCount == 2 || mViewCount == 3) { if (mMiddleValues != null && mMiddleValues.length != 0) { if (mMiddleValues.length < DATA_SIZE) { for (int i = 0; i < mMiddleValues.length; i++) { mShowingMiddle[i] = mMiddleValues[i]; } for (int i = mMiddleValues.length; i < DATA_SIZE; i++) { mShowingMiddle[i] = -9999; } } else { for (int i = 0; i < DATA_SIZE; i++) { mShowingMiddle[i] = mMiddleValues[i]; } } mNpMiddle.setMinValue(0); mNpMiddle.setMaxValue(DATA_SIZE - 1); if (mDefaultMiddleValue != null) updateMiddleView(mDefaultMiddleValue); else updateMiddleView(mShowingMiddle[0]); } } /** * 右側控件 */ if (mViewCount == 3) { if (mRightValues != null && mRightValues.length != 0) { if (mRightValues.length < DATA_SIZE) { for (int i = 0; i < mRightValues.length; i++) { mShowingRight[i] = mRightValues[i]; } for (int i = mRightValues.length; i < DATA_SIZE; i++) { mShowingRight[i] = -9999; } } else { for (int i = 0; i < DATA_SIZE; i++) { mShowingRight[i] = mRightValues[i]; } } mNpRight.setMinValue(0); mNpRight.setMaxValue(DATA_SIZE - 1); if (mDefaultRightValue != null) updateRightView(mDefaultRightValue); else updateRightView(mShowingRight[0]); } } } private void updateLeftView(Object value) { updateValue(value, 0); } private void updateMiddleView(Object value) { updateValue(value, 1); } private void updateRightView(Object value) { updateValue(value, 2); } /** * 更新滾輪視圖 * * @param value * @param index */ private void updateValue(Object value, int index) { String showStr[] = new String[DATA_SIZE]; MyNumberPicker picker; Object[] showingValue; Object[] values; int step; if (index == 0) { picker = mNpLeft; showingValue = mShowingLeft; values = mLeftValues; step = mLeftStep; } else if (index == 1) { picker = mNpMiddle; showingValue = mShowingMiddle; values = mMiddleValues; step = mMiddleStep; } else { picker = mNpRight; showingValue = mShowingRight; values = mRightValues; step = mRightStep; } if (values instanceof Integer[]) { for (int i = 0; i < DATA_SIZE; i++) { showingValue[i] = (int) value - step * (DATA_SIZE / 2 - i); int offset = (int) values[values.length - 1] - (int) values[0] + step; if ((int) showingValue[i] < (int) values[0]) { showingValue[i] = (int) showingValue[i] + offset; } if ((int) showingValue[i] > (int) values[values.length - 1]) { showingValue[i] = (int) showingValue[i] - offset; } showStr[i] = "" + showingValue[i]; } } else { int strIndex = 0; for (int i = 0; i < values.length; i++) { if (values[i].equals(value)) { strIndex = i; break; } } for (int i = 0; i < DATA_SIZE; i++) { int temp = strIndex - (DATA_SIZE / 2 - i); if (temp < 0) { temp += values.length; } if (temp >= values.length) { temp -= values.length; } showingValue[i] = values[temp]; showStr[i] = (String) values[temp]; } } picker.setDisplayedValues(showStr); picker.setValue(DATA_SIZE / 2); picker.postInvalidate(); } @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { if (picker == mNpLeft) { updateLeftView(mShowingLeft[newVal]); } else if (picker == mNpMiddle) { updateMiddleView(mShowingMiddle[newVal]); } else if (picker == mNpRight) { updateRightView(mShowingRight[newVal]); } if (mSelectedChangeListener != null) { mSelectedChangeListener.onSelected(this, mShowingLeft[DATA_SIZE / 2], mShowingMiddle[DATA_SIZE / 2], mShowingRight[DATA_SIZE / 2]); } } /** * 設置數據--單列數據 * * @param leftValues * @param mDefaultLeftValue */ public void setValueData(Object[] leftValues, Object mDefaultLeftValue) { this.mViewCount = 1; this.mLeftValues = leftValues; this.mDefaultLeftValue = mDefaultLeftValue; initViewAndPicker(); } /** * 設置數據--兩列數據 * * @param leftValues * @param mDefaultLeftValue * @param middleValues * @param defaultMiddleValue */ public void setValueData(Object[] leftValues, Object mDefaultLeftValue, Object[] middleValues, Object defaultMiddleValue) { this.mViewCount = 2; this.mLeftValues = leftValues; this.mDefaultLeftValue = mDefaultLeftValue; this.mMiddleValues = middleValues; this.mDefaultMiddleValue = defaultMiddleValue; initViewAndPicker(); } /** * 設置數據--三列數據 * * @param leftValues * @param mDefaultLeftValue * @param middleValues * @param defaultMiddleValue * @param rightValues * @param defaultRightValue */ public void setValueData(Object[] leftValues, Object mDefaultLeftValue, Object[] middleValues, Object defaultMiddleValue, Object[] rightValues, Object defaultRightValue) { this.mViewCount = 3; this.mLeftValues = leftValues; this.mDefaultLeftValue = mDefaultLeftValue; this.mMiddleValues = middleValues; this.mDefaultMiddleValue = defaultMiddleValue; this.mRightValues = rightValues; this.mDefaultRightValue = defaultRightValue; initViewAndPicker(); } /** * 設置左邊數據步長 * * @param step */ public void setLeftStep(int step) { this.mLeftStep = step; initViewAndPicker(); } /** * 設置中間數據步長 * * @param step */ public void setMiddleStep(int step) { this.mMiddleStep = step; initViewAndPicker(); } /** * 設置右邊數據步長 * * @param step */ public void setRightStep(int step) { this.mRightStep = step; initViewAndPicker(); } /** * 設置標題 * * @param left * @param middle * @param right */ public void setTitle(String left, String middle, String right) { if (left != null) { mTitleLeft.setVisibility(VISIBLE); mTitleLeft.setText(left); } else { mTitleLeft.setVisibility(GONE); } if (middle != null) { mTitleMiddle.setVisibility(VISIBLE); mTitleMiddle.setText(middle); } else { mTitleMiddle.setVisibility(GONE); } if (right != null) { mTitleRight.setVisibility(VISIBLE); mTitleRight.setText(right); } else { mTitleRight.setVisibility(GONE); } this.postInvalidate(); } public void setUnitLeft(String unitLeft) { setUnit(unitLeft, 0); } public void setmUnitMiddle(String unitMiddle) { setUnit(unitMiddle, 1); } public void setUnitRight(String unitRight) { setUnit(unitRight, 2); } private void setUnit(String unit, int index) { TextView tvUnit; if (index == 0) { tvUnit = mUnitLeft; } else if (index == 1) { tvUnit = mUnitMiddle; } else { tvUnit = mUnitRight; } if (unit != null) { tvUnit.setText(unit); } else { tvUnit.setText(" "); } initViewAndPicker(); } /** * 設置回調 * * @param listener */ public void setOnSelectedChangeListener(onSelectedChangeListener listener) { this.mSelectedChangeListener = listener; } /** * dp轉px * * @param dp * @return */ private int dip2px(int dp) { float scale = mContext.getResources().getDisplayMetrics().density; return (int) (scale * dp + 0.5f); } /** * 回調接口 */ public interface onSelectedChangeListener { void onSelected(PickValueView view, Object leftValue, Object middleValue, Object rightValue); } }
關於NumberPicker
默認的NumberPicker往往字體顏色、分割線顏色等都是跟隨系統,不能改變,考慮到可能比較丑或者有其他需求,所以自定義的NumberPicker,通過反射的方式更改裡面的一些屬性,代碼如下:
package com.example.moore.picktimeview.widget; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageButton; import android.widget.NumberPicker; import java.lang.reflect.Field; /** * Created by Moore on 2016/10/20. */ public class MyNumberPicker extends NumberPicker { private static int mTextSize = 16; private static int mTextColor = 0x000000; private static int mDividerColor = 0xFFFF00; public MyNumberPicker(Context context) { super(context); setNumberPickerDividerColor(); } public MyNumberPicker(Context context, AttributeSet attrs) { super(context, attrs); setNumberPickerDividerColor(); } public MyNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setNumberPickerDividerColor(); } @Override public void addView(View child) { super.addView(child); updateView(child); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); updateView(child); } @Override public void addView(View child, ViewGroup.LayoutParams params) { super.addView(child, params); updateView(child); } private void updateView(View view) { if (view instanceof EditText) { // ((EditText) view).setTextSize(mTextSize); ((EditText) view).setTextSize(17); // ((EditText) view).setTextColor(mTextColor); ((EditText) view).setTextColor(Color.parseColor("#6495ED")); } } private void setNumberPickerDividerColor() { Field[] pickerFields = NumberPicker.class.getDeclaredFields(); /** * 設置分割線顏色 */ for (Field pf : pickerFields) { if (pf.getName().equals("mSelectionDivider")) { pf.setAccessible(true); try { // pf.set(this, new ColorDrawable(mDividerColor)); pf.set(this, new ColorDrawable(Color.parseColor("#C4C4C4"))); } catch (IllegalAccessException e) { e.printStackTrace(); } break; } } /** * 設置分割線高度 */ for (Field pf : pickerFields) { if (pf.getName().equals("mSelectionDividerHeight")) { pf.setAccessible(true); try { pf.set(this, 2); } catch (IllegalAccessException e) { e.printStackTrace(); } break; } } for (Field pf : pickerFields) { if (pf.getName().equals("mSelectorElementHeight")) { pf.setAccessible(true); try { pf.set(this, 2); } catch (IllegalAccessException e) { e.printStackTrace(); } break; } } } public void setDividerColor(int color) { this.mDividerColor = color; // this.postInvalidate(); } public void setTextColor(int color) { this.mTextColor = color; // this.postInvalidate(); } public void setTextSize(int textSize) { this.mTextSize = textSize; // this.postInvalidate(); } }
完整Demo可前往github查看與下載,地址:https://github.com/lizebinbin/PickTimeView.git 謝謝!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
本文實例講述了android實現listview分頁的方法。分享給大家供大家參考。具體分析如下:最近做了下listview的分頁,跟WEB上的分頁是一個意思,需要那幾個分
最近使用到Recylerview完成拖動排序,側滑刪除,在此記錄一下。需要使用到:ItemTouchHelper.Callback這個類。效果圖:在有Recy
在Andoird使用Android自帶的那些組件,像SlidingDrawer和DrawerLayout都是抽屜效果的菜單,但是在項目很多要實現的功能都收到Android
1.界面設置默認的 Android Studio 為灰色界面,可以選擇使用炫酷的黑色界面。Settings --> Appearance --> Theme