天天看點

Android 年月選擇器,基于必酷公司的豎直滑動選擇器WheelView進行實作重複造輪子?說一說實作過程附以完整源碼

文章目錄

  • 重複造輪子?
  • 說一說實作過程
    • 內建WheelView庫
    • DatePicker的實作
      • DatePicker
      • Builder
      • OnDatePickedListener
      • WheelView
    • DatePicker的使用
  • 附以完整源碼
    • DatePicker.class
    • layout_datepicker.xml
明日複明日,明日何其多————困了,但不能把工作推到明天了,拭心大佬是我學習的榜樣。

重複造輪子?

也不算是。

Bigkoo的Android-PickerView庫,很好的支援了日期、時間、地區等各種類型的關聯選擇。目前的需求是隻需要年月二級關聯查詢,不想內建整個Android-PickerView,畢竟大部分功能用不到,白白增加代碼量,于是就基于Bigkoo的WheelView庫實作了一個簡便的年月選擇器DatePicker。

說一說實作過程

內建WheelView庫

implementation 'com.contrarywind:wheelview:4.0.9'
           

Bigkoo的Github位址:Bigkoo Github

他的Android-PickerView庫寫的很棒,功能全,性能好,單看看Star數,就知道此庫得到了Android開發者的廣泛認可。

WheelView内置于Android-PickerView,也可單獨內建。它是一個自定義View,800行源碼,立一個flag:讀它的源碼,下周内出一篇源碼分析的文章。

DatePicker的實作

DatePicker

DatePicker繼承于DialogFragment。采用Fragment彈出框,更便于自定義DatePicker布局,以及在任意頁面進行調用,并可像管理Fragment一樣管理DatePicker,很友善。

Builder

在DatePicker中用了一個靜态内部類Builder,用于建構DatePicker及初始化。在Builder中開放了幾個方法:

  1. setYearRange(int minYear, int maxYear)

    設定年份可選擇的範圍

  2. setCancelable(boolean cancelable)

    設定點選DatePicker外區域是否可取消DatePicker

  3. loopYear(boolean loop)

    設定年份是否可循環展示,預設為true

  4. loopMonth(boolean loop)

    設定月份是否可循環展示,預設為true

  5. build()

    建構并初始化DatePicker

public static class Builder {

        private OnDatePickedListener listener;

        public Builder(OnDatePickedListener listener) {
            this.listener = listener;
        }

        private int minYear;
        private int maxYear;
        private boolean loopYear;
        private boolean loopMonth;
        private boolean cancelable;

        public Builder setYearRange(int minYear, int maxYear) {
            this.minYear = minYear;
            this.maxYear = maxYear;
            return this;
        }

        public Builder setCancelable(boolean cancelable) {
            this.cancelable = cancelable;
            return this;
        }

        public Builder loopYear(boolean loop) {
            this.loopYear = loop;
            return this;
        }

        public Builder loopMonth(boolean loop) {
            this.loopMonth = loop;
            return this;
        }

        public DatePicker build() {
            if (minYear > maxYear) {
                throw new IllegalArgumentException();//不允許minYear > maxYear
            }
            return DatePicker.newInstance(this);
        }

    }
           

OnDatePickedListener

DatePicker的監聽接口,内有一回調方法onDatePickCompleted(int year, int month),用于年月選擇後的選擇值回傳。

public interface OnDatePickedListener {
   void onDatePickCompleted(int year, int month);
}
           

WheelView

xml布局:

<com.contrarywind.view.WheelView
            android:id="@+id/wheelview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
           

Java代碼:

WheelView wheelView = findViewById(R.id.wheelview);

        wheelView.setCyclic(false); //是否循環展示

        final List<String> mOptionsItems = new ArrayList<>();
        mOptionsItems.add("item0");
        mOptionsItems.add("item1");
        mOptionsItems.add("item2");
  
        wheelView.setAdapter(new ArrayWheelAdapter(mOptionsItems));
        wheelView.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int index) {
                Toast.makeText(MainActivity.this, "" + mOptionsItems.get(index), Toast.LENGTH_SHORT).show();
            }
        });
           

DatePicker的使用

private String TAG = DemoActivity.class.getName();
           
DatePicker datePicker = new DatePicker.Builder(new DatePicker.OnDatePickedListener() {
                    @Override
                    public void onDatePickCompleted(int year, int month) {
                        Toast.makeText(LoginStatisticsActivity.this, "selectedYear:" + year + "--selectedMonth" + month, Toast.LENGTH_SHORT).show();
                    }
                })
                  .setYearRange(1990, Calendar.getInstance().get(Calendar.YEAR))//1990——目前年份
                  .setCancelable(true)
                  .loopYear(false)
                  .loopMonth(false)
                  .build();
datePicker.showPicker(DemoActivity.this, TAG);
           

附以完整源碼

DatePicker.class

package com.gd.terminalmanager.view;

import android.app.Activity;
import android.app.DialogFragment;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;

import com.contrarywind.adapter.WheelAdapter;
import com.contrarywind.listener.OnItemSelectedListener;
import com.contrarywind.view.WheelView;
import com.gd.terminalmanager.R;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * 隻提供年和月的選擇
 *
 * @author ZuoHailong
 * @date 2019/3/19.
 */
public class DatePicker extends DialogFragment {

    private static final int DEFAULT_MAX_YEAR = 2050;
    private static final int DEFAULT_MIN_YEAR = 1900;

    private WheelView wheelViewYear, wheelViewMonth;
    private TextView tv_sure, tv_cancle;

    private int minYear; // min year
    private int maxYear; // max year
    private boolean cancelable, loopYear, loopMonth;
    private OnDatePickedListener listener;
    private int selectedYear, selectedMonth;
    private static Calendar calendar;

    //private,隻允許通過Builder建構DatePicker
    private static DatePicker newInstance(Builder builder) {
        calendar = Calendar.getInstance();
        DatePicker datePicker = new DatePicker();
        datePicker.minYear = builder.minYear < DatePicker.DEFAULT_MIN_YEAR ? DatePicker.DEFAULT_MIN_YEAR : builder.minYear;
        datePicker.maxYear = builder.maxYear > DatePicker.DEFAULT_MAX_YEAR ? DatePicker.DEFAULT_MAX_YEAR : builder.maxYear;
        datePicker.cancelable = builder.cancelable;
        datePicker.loopYear = builder.loopYear;
        datePicker.loopMonth = builder.loopMonth;
        datePicker.listener = builder.listener;
        return datePicker;
    }

    @Override
    public void onStart() {
        super.onStart();
        DisplayMetrics dm = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
        getDialog().getWindow().setLayout(dm.widthPixels, getDialog().getWindow().getAttributes().height);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.layout_datepicker, container);
        tv_cancle = view.findViewById(R.id.tv_cancle);
        tv_cancle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });
        tv_sure = view.findViewById(R.id.tv_sure);
        tv_sure.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onDatePickCompleted(selectedYear, selectedMonth);
                dismiss();
            }
        });
        wheelViewYear = view.findViewById(R.id.wheelViewYear);
        wheelViewMonth = view.findViewById(R.id.wheelViewMonth);
        //設定DialogFragment所在視窗的背景透明
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
        initView();
        return view;
    }

    private void initView() {
        setCancelable(cancelable);
        initYear(minYear, maxYear);
        initMonth();
        //預設選擇目前年份
        selectedYear = calendar.get(Calendar.YEAR);
        //position = 目前年份 - minYear
        wheelViewYear.setCurrentItem(selectedYear - minYear);
        //設定是否循環顯示年份
        wheelViewYear.setCyclic(loopYear);
        wheelViewYear.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int position) {
                selectedYear = yearList.get(position);
            }
        });
        //月份是從0到11,實際選擇月份要+1
        selectedMonth = calendar.get(Calendar.MONTH) + 1;
        //position = 實際選擇月份 - 1
        wheelViewMonth.setCurrentItem(selectedMonth - 1);
        //設定是否循環顯示月份
        wheelViewMonth.setCyclic(loopMonth);
        wheelViewMonth.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int position) {
                selectedMonth = monthList.get(position);
            }
        });
    }

    //存儲可供選擇的 年份/月份 集合
    private List<Integer> yearList, monthList;

    private void initYear(int minYear, int maxYear) {
        //避免傳入順序有誤
        minYear = Math.min(minYear, maxYear);
        maxYear = Math.max(minYear, maxYear);

        yearList = new ArrayList<>();
        for (int i = 0; i < maxYear + 1 - minYear; i++) {
            yearList.add(minYear + i);
        }
        wheelViewYear.setAdapter(new WheelAdapter() {
            @Override
            public int getItemsCount() {
                return yearList.size();
            }

            @Override
            public Object getItem(int index) {
                return yearList.get(index);
            }

            @Override
            public int indexOf(Object o) {
                return yearList.indexOf(o);
            }
        });
    }

    private void initMonth() {
        monthList = new ArrayList<>();
        monthList.add(1);
        monthList.add(2);
        monthList.add(3);
        monthList.add(4);
        monthList.add(5);
        monthList.add(6);
        monthList.add(7);
        monthList.add(8);
        monthList.add(9);
        monthList.add(10);
        monthList.add(11);
        monthList.add(12);

        wheelViewMonth.setAdapter(new WheelAdapter() {
            @Override
            public int getItemsCount() {
                return monthList.size();
            }

            @Override
            public Object getItem(int index) {
                return monthList.get(index);
            }

            @Override
            public int indexOf(Object o) {
                return monthList.indexOf(o);
            }
        });
    }

    //顯示DatePicker
    public void showPicker(Activity mActivity, String TAG) {
        show(mActivity.getFragmentManager(), TAG);
    }

    public interface OnDatePickedListener {
        void onDatePickCompleted(int year, int month);
    }

    public static class Builder {

        private OnDatePickedListener listener;

        public Builder(OnDatePickedListener listener) {
            this.listener = listener;
        }

        private int minYear;
        private int maxYear;
        private boolean loopYear;
        private boolean loopMonth;
        private boolean cancelable;

        public Builder setYearRange(int minYear, int maxYear) {
            this.minYear = minYear;
            this.maxYear = maxYear;
            return this;
        }

        public Builder setCancelable(boolean cancelable) {
            this.cancelable = cancelable;
            return this;
        }

        public Builder loopYear(boolean loop) {
            this.loopYear = loop;
            return this;
        }

        public Builder loopMonth(boolean loop) {
            this.loopMonth = loop;
            return this;
        }

        public DatePicker build() {
            if (minYear > maxYear) {
                throw new IllegalArgumentException();//不允許minYear > maxYear
            }
            return DatePicker.newInstance(this);
        }

    }

}

           

layout_datepicker.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#ffffff"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="40dp">

            <TextView
                android:id="@+id/tv_sure"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_alignParentRight="true"
                android:gravity="center"
                android:paddingLeft="20dp"
                android:paddingRight="20dp"
                android:text="确定"
                android:textSize="17sp" />

            <TextView
                android:id="@+id/tv_cancle"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_alignParentLeft="true"
                android:gravity="center"
                android:paddingLeft="20dp"
                android:paddingRight="20dp"
                android:text="取消"
                android:textSize="17sp" />

        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="#eeeeee" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <com.contrarywind.view.WheelView
                android:id="@+id/wheelViewYear"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1" />

            <com.contrarywind.view.WheelView
                android:id="@+id/wheelViewMonth"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1" />
        </LinearLayout>


    </LinearLayout>

</RelativeLayout>
           

繼續閱讀