1. 程式人生 > >Android View 滾輪控制元件LoopView+自定義Dialog [時間地域選擇器] Picker

Android View 滾輪控制元件LoopView+自定義Dialog [時間地域選擇器] Picker


發現了一些好的東西: 

https://github.com/weidongjian/androidWheelView

曾經找到過 WheelView。當時江湖救急,直接用了。資料來源太大的話會導致效能降低。
當時有吐槽如果有使用自定義view或者繼承ListView、RecyclerView的就好。
今日找到了使用自定義view方法寫的。


紅色為原始碼, 藍色為哥寫的對應擴充套件。
核心原始碼為LoopView,本來想看看實現原理方法,奈何原始碼被下了毒。
不過值得慶幸的是核心api還在


有了這些東西那麼就可以自己進行一些定製了。


public class DataPickerDialog extends Dialog {

    private Params params;

    public DataPickerDialog(Context context, int themeResId) {
        super(context, themeResId);
    }

    private void setParams(DataPickerDialog.Params params) {
        this.params = params;
    }

    public interface OnDataSelectedListener {
        void onDataSelected(String itemValue);
    }

    private static final class Params {
        private boolean shadow = true;
        private boolean canCancel = true;
        private LoopView loopData;
        private String title; 
        private String unit;
        private int initSelection;
        private OnDataSelectedListener callback;
        private final List<String> dataList = new ArrayList<>();
    }

    public static class Builder {
        private final Context context;
        private final DataPickerDialog.Params params;

        public Builder(Context context) {
            this.context = context;
            params = new DataPickerDialog.Params();
        }

        private final String getCurrDateValue() {
            return params.loopData.getCurrentItemValue();
        }

        public Builder setData(List<String> dataList) {
            params.dataList.clear();
            params.dataList.addAll(dataList);
            return this;
        }

        public Builder setTitle(String title) {
            params.title = title;
            return this;
        }

        public Builder setUnit(String unit) {
            params.unit = unit;
            return this;
        }

        public Builder setSelection(int selection) {
            params.initSelection = selection;
            return this;
        }

        public Builder setOnDataSelectedListener(OnDataSelectedListener onDataSelectedListener) {
            params.callback = onDataSelectedListener;
            return this;
        }


        public DataPickerDialog create() {
            final DataPickerDialog dialog = new DataPickerDialog(context, params.shadow ? R.style.Theme_Light_NoTitle_Dialog : R.style.Theme_Light_NoTitle_NoShadow_Dialog);
            View view = LayoutInflater.from(context).inflate(R.layout.layout_picker_data, null);

            if (!TextUtils.isEmpty(params.title)) {
                TextView txTitle = (TextView) view.findViewById(R.id.tx_title);
                txTitle.setText(params.title);
            }
            if (!TextUtils.isEmpty(params.unit)) {
                TextView txUnit = (TextView) view.findViewById(R.id.tx_unit);
                txUnit.setText(params.unit);
            }

            final LoopView loopData = (LoopView) view.findViewById(R.id.loop_data);
            loopData.setArrayList(params.dataList);
            loopData.setNotLoop();
            if (params.dataList.size() > 0) loopData.setCurrentItem(params.initSelection);
            view.findViewById(R.id.tx_finish).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dialog.dismiss();
                    if (params.callback != null) params.callback.onDataSelected(getCurrDateValue());
                }
            });


            Window win = dialog.getWindow();
            win.getDecorView().setPadding(0, 0, 0, 0);
            WindowManager.LayoutParams lp = win.getAttributes();
            lp.width = WindowManager.LayoutParams.MATCH_PARENT;
            lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
            win.setAttributes(lp);
            win.setGravity(Gravity.BOTTOM);
            win.setWindowAnimations(R.style.Animation_Bottom_Rising);

            dialog.setContentView(view);
            dialog.setCanceledOnTouchOutside(params.canCancel);
            dialog.setCancelable(params.canCancel);

            params.loopData = loopData;
            dialog.setParams(params);

            return dialog;
        }
    }
}


啊Builder - 自定義Dialog 標配程式碼走起。

那麼在Activity中


private final void showDialog() {
        DataPickerDialog.Builder builder = new DataPickerDialog.Builder(this);
        List<String> data = Arrays.asList(new String[]{"a", "b", "c", "d", "e", "f", "g", "h"});

        DataPickerDialog dialog = builder.setUnit("單位").setData(data).setSelection(1).setTitle("標題")
                .setOnDataSelectedListener(new DataPickerDialog.OnDataSelectedListener() {
                    @Override
                    public void onDataSelected(String itemValue) {
                        Toast.makeText(getApplicationContext(), itemValue, Toast.LENGTH_SHORT).show();
                    }
                }).create();

        dialog.show();
    }
以上標準通用的選擇器就可以直接用上啦。
時間選擇器
ta的不同之處在於日期方面需要聯動。選擇了月份後,日期可能要變。
比如選擇了31號後切換月份到2月。
這裡需要實現LoopListener來監聽一些邏輯,再使用Calender進行控制。

public DatePickerDialog create() {
            final DatePickerDialog dialog = new DatePickerDialog(context, params.shadow ? R.style.Theme_Light_NoTitle_Dialog : R.style.Theme_Light_NoTitle_NoShadow_Dialog);
            View view = LayoutInflater.from(context).inflate(R.layout.layout_picker_date, null);

            final LoopView loopDay = (LoopView) view.findViewById(R.id.loop_day);
            loopDay.setArrayList(d(1, 30));
            loopDay.setCurrentItem(15);
            loopDay.setNotLoop();

            Calendar c = Calendar.getInstance();
            int year = c.get(Calendar.YEAR);
            final LoopView loopYear = (LoopView) view.findViewById(R.id.loop_year);
            loopYear.setArrayList(d(MIN_YEAR, year - MIN_YEAR + 1));
            loopYear.setCurrentItem(year - MIN_YEAR - 25);
            loopYear.setNotLoop();

            final LoopView loopMonth = (LoopView) view.findViewById(R.id.loop_month);
            loopMonth.setArrayList(d(1, 12));
            loopMonth.setCurrentItem(6);
            loopMonth.setNotLoop();

            final LoopListener maxDaySyncListener = new LoopListener() {
                @Override
                public void onItemSelect(int item) {
                    Calendar c = Calendar.getInstance();
                    c.set(Integer.parseInt(loopYear.getCurrentItemValue()), Integer.parseInt(loopMonth.getCurrentItemValue()) - 1, 1);
                    c.roll(Calendar.DATE, false);
                    int maxDayOfMonth = c.get(Calendar.DATE);
                    int fixedCurr = loopDay.getCurrentItem();
                    loopDay.setArrayList(d(1, maxDayOfMonth));
                    // 修正被選中的日期最大值
                    if (fixedCurr > maxDayOfMonth) fixedCurr = maxDayOfMonth - 1;
                    loopDay.setCurrentItem(fixedCurr);
                }
            };
            loopYear.setListener(maxDaySyncListener);
            loopMonth.setListener(maxDaySyncListener);

            view.findViewById(R.id.tx_finish).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dialog.dismiss();
                    params.callback.onDateSelected(getCurrDateValues());
                }
            });

            Window win = dialog.getWindow();
            win.getDecorView().setPadding(0, 0, 0, 0);
            WindowManager.LayoutParams lp = win.getAttributes();
            lp.width = WindowManager.LayoutParams.MATCH_PARENT;
            lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
            win.setAttributes(lp);
            win.setGravity(Gravity.BOTTOM);
            win.setWindowAnimations(R.style.Animation_Bottom_Rising);

            dialog.setContentView(view);
            dialog.setCanceledOnTouchOutside(params.canCancel);
            dialog.setCancelable(params.canCancel);

            params.loopYear = loopYear;
            params.loopMonth = loopMonth;
            params.loopDay = loopDay;
            dialog.setParams(params);

            return dialog;
}
maxDaySyncListener  將呼叫 loopDay.setCurrentItem() 修正日期的錯誤。
理所當然在年份和月份上加上這個檢測。
loopYear.setListener(maxDaySyncListener);
loopMonth.setListener(maxDaySyncListener);

地域選擇器
ta的不同之處在於資料來源,然後聯動。
Map<String, List<String>>
Key表示城市.value表示區域


public Builder(Context context) {
            this.context = context;
            params = new RegionPickerDialog.Params();

            try {
                InputStreamReader inputReader = new InputStreamReader(context.getAssets().open("city_data.json"));
                BufferedReader bufReader = new BufferedReader(inputReader);
                String line = "";
                StringBuffer result = new StringBuffer();
                while ((line = bufReader.readLine()) != null) {
                    result.append(line);
                }
                params.dataList = new Gson().fromJson(result.toString(), new TypeToken<Map<String, List<String>>>() {
                }.getType());
            } catch (Exception e) {
                Log.e("RegionPickerDialog", "The Region source file does not exist or has been damaged");
                params.dataList = new HashMap<>();
            }
}

那這裡我從 assets 中取去資料來源。 
city_data.json中的資料 like this:
{
    "北京": ["東城區","西城區","海淀區",...],
    "新疆": ["烏魯木齊","克拉瑪依","阿勒泰"],
    "重慶": ["渝中區","大渡口區", ...
    ....
}


最後程式碼在 dialog-picker中,大家可以進行自己的定製哦,UI 邏輯什麼的。

程式碼地址

https://github.com/iielse/pickerDialog

另外推薦 

https://github.com/AigeStudio/WheelPicker