Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> DatePicker

DatePicker

編輯:關於Android編程

DatePicker實戰

效果圖:

\

依賴導入<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> compile 'cn.aigestudio.datepicker:DatePicker:2.2.0'

DatePicker直接在xml布局使用,代碼進行相關的初始化配置監聽設置等。(setData方法必須調用進行初始化)

......
DatePicker datePicker = (DatePicker) findViewById(R.id.datePicker);
datePicker.setDate(mYear, mMonth);
datePicker.setOnDateSelectedListener(new DatePicker.OnDateSelectedListener() {
    @Override
    public void onDateSelected(List date) {

    }
});
......

DatePicker支持多種模式:單選、多選、正常模式,具體定義如下

public enum DPMode {
    SINGLE, MULTIPLE, NONE
}

DatePicker不同的模式通過不同的監聽回調事件,獲取選中的時間值

setOnDateSelectedListener // 多選監聽

setOnDatePickedListener // 單選監聽

選擇後的日期將會以列表(多選模式下)或字符串(單選模式下)的形式返回,日期字符串的格式為:

格式:yyyy-M-d 
示例:2015-3-28

DatePicker有一套自己的默認UI,默認的Color配置,如果你使用Module導入工程,可以直接修改默認配置,關聯類如下

\

DatePicker默認天朝的節日背景色

當然你也可以自己修改定制,同時還可以為特殊節日添加浮標,首先添加指定需要修改號數集,添加到緩存到對應map集合

        List tmpTR = new ArrayList<>();
        tmpTR.add("2015-10-10");
        tmpTR.add("2015-10-11");
        tmpTR.add("2015-10-12");
        tmpTR.add("2015-10-13");
        tmpTR.add("2015-10-14");
        tmpTR.add("2015-10-15");
        tmpTR.add("2015-10-16");
        DPCManager.getInstance().setDecorTR(tmpTR);

    private static final HashMap> DECOR_CACHE_BG = new HashMap<>();
    private static final HashMap> DECOR_CACHE_TL = new HashMap<>();
    private static final HashMap> DECOR_CACHE_T = new HashMap<>();
    private static final HashMap> DECOR_CACHE_TR = new HashMap<>();
    private static final HashMap> DECOR_CACHE_L = new HashMap<>();
    private static final HashMap> DECOR_CACHE_R = new HashMap<>();

完成上述步驟後,再通過setDPDecor方法繪制對應號數的浮標

  picker.setDPDecor(new DPDecor() {
            @Override
            public void drawDecorTL(Canvas canvas, Rect rect, Paint paint, String data) {
                super.drawDecorTL(canvas, rect, paint, data);
                switch (data) {
                    case "2015-10-5":
                    case "2015-10-7":
                    case "2015-10-9":
                    case "2015-10-11":
                        paint.setColor(Color.GREEN);
                        canvas.drawRect(rect, paint);
                        break;
                    default:
                        paint.setColor(Color.RED);
                        canvas.drawCircle(rect.centerX(), rect.centerY(), rect.width() / 2, paint);
                        break;
                }
            }

            @Override
            public void drawDecorTR(Canvas canvas, Rect rect, Paint paint, String data) {
                super.drawDecorTR(canvas, rect, paint, data);
                switch (data) {
                    case "2015-10-10":
                    case "2015-10-11":
                    case "2015-10-12":
                        paint.setColor(Color.BLUE);
                        canvas.drawCircle(rect.centerX(), rect.centerY(), rect.width() / 2, paint);
                        break;
                    default:
                        paint.setColor(Color.YELLOW);
                        canvas.drawRect(rect, paint);
                        break;
                }
            }
        });

繪制的浮標可以是不同形狀不同顏色,具體怎麼關聯到月視圖稍後再看,繪制的浮標可以是不同方向,具體使用參考DPDecor類的定義。(具體使用參考上面代碼塊實例)

\

繪制浮標效果如下圖:

更為詳盡的使用請參考Aige官方示例:https://github.com/AigeStudio/DatePicker

DataPicker源碼淺析

\

calendars包內部提供方法都有中文注釋,相信都能看懂,關於一些節日號數相關基礎數據的獲取判斷,內部的一些算法,表示無能為力(我算法白癡),DPDecor定義方法上面有提到,具體使用不詳解。

抽象類DPLManager是一個語言管理方面的,該庫支持中英文的日歷,根據CH標示采取不同的實現

theme模塊關於UI配置方面的,上面有提到,具體代碼設置color,請參考源碼,頭有中文注釋一目了然。

日歷數據實體,封裝日歷繪制時需要的數據(封裝按照二維數組,每個月可能存在的行列進行封裝)


public class DPInfo {
    public String strG, strF;
    public boolean isHoliday;
    public boolean isToday, isWeekend;
    public boolean isSolarTerms, isFestival, isDeferred;
    public boolean isDecorBG;
    public boolean isDecorTL, isDecorT, isDecorTR, isDecorL, isDecorR;
}

\

utils包裡面就一個dp px的轉換和一維數組轉二維數組

整個庫的UI核心是下面兩個類

DatePicker

MonthView

DatePicker自定義的Layout容器,構造函數先添加標題欄顯示控件和星期-到星期日的顯示視圖,最後添加月視圖

 public DatePicker(Context context, AttributeSet attrs) {
        super(context, attrs);
        //..............略過些許方法................
        mTManager = DPTManager.getInstance();
        mLManager = DPLManager.getInstance();

        // 設置排列方向為豎向
        setOrientation(VERTICAL);
        // 標題欄根布局
        RelativeLayout rlTitle = new RelativeLayout(context);

        // 周視圖根布局
        LinearLayout llWeek = new LinearLayout(context);

        // 標題欄子元素布局參數
        RelativeLayout.LayoutParams lpYear =
                new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);

        // --------------------------------------------------------------------------------標題欄
        // 年份顯示
        tvYear = new TextView(context);

        // 月份顯示
        tvMonth = new TextView(context);
        tvMonth.setText("六月");

        // 確定顯示
        tvEnsure = new TextView(context);

        tvEnsure.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != onDateSelectedListener) {
                    onDateSelectedListener.onDateSelected(monthView.getDateSelected());
                }
            }
        });

        rlTitle.addView(tvYear, lpYear);
        rlTitle.addView(tvMonth, lpMonth);
        rlTitle.addView(tvEnsure, lpEnsure);

        addView(rlTitle, llParams);

        // --------------------------------------------------------------------------------周視圖
        for (int i = 0; i < mLManager.titleWeek().length; i++) {
            TextView tvWeek = new TextView(context);
            tvWeek.setText(mLManager.titleWeek()[i]);
            tvWeek.setGravity(Gravity.CENTER);
            tvWeek.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
            tvWeek.setTextColor(mTManager.colorTitle());
            llWeek.addView(tvWeek, lpWeek);
        }
        addView(llWeek, llParams);

        // ------------------------------------------------------------------------------------月視圖
        monthView = new MonthView(context);
        monthView.setOnDateChangeListener(new MonthView.OnDateChangeListener() {
            @Override
            public void onMonthChange(int month) {
                tvMonth.setText(mLManager.titleMonth()[month - 1]);
            }

            @Override
            public void onYearChange(int year) {
                String tmp = String.valueOf(year);
                if (tmp.startsWith("-")) {
                    tmp = tmp.replace("-", mLManager.titleBC());
                }
                tvYear.setText(tmp);
            }
        });
        addView(monthView, llParams);
    }

設置日歷選擇模式如果支持多選則顯示確定按鈕

  /**
     * 設置日期選擇模式
     *
     * @param mode ...
     */
    public void setMode(DPMode mode) {
        if (mode != DPMode.MULTIPLE) {
            tvEnsure.setVisibility(GONE);
        }
        monthView.setDPMode(mode);
    }

提供多個set方法,本質是調用了月視圖View進行set,特別是對於DatePicker的監聽綁定做了判斷。Aige的使用文檔是這樣說的

\

MonthView默認多選模式,在沒有設置模式的情況下直接設置單選監聽會拋出異常,反之同理

  /**
     * 設置單選監聽器
     *
     * @param onDatePickedListener ...
     */
    public void setOnDatePickedListener(OnDatePickedListener onDatePickedListener) {
        if (monthView.getDPMode() != DPMode.SINGLE) {
            throw new RuntimeException(
                    "Current DPMode does not SINGLE! Please call setMode set DPMode to SINGLE!");
        }
        monthView.setOnDatePickedListener(onDatePickedListener);
    }

    /**
     * 設置多選監聽器
     *
     * @param onDateSelectedListener ...
     */
    public void setOnDateSelectedListener(OnDateSelectedListener onDateSelectedListener) {
        if (monthView.getDPMode() != DPMode.MULTIPLE) {
            throw new RuntimeException(
                    "Current DPMode does not MULTIPLE! Please call setMode set DPMode to MULTIPLE!");
        }
        this.onDateSelectedListener = onDateSelectedListener;
    }

MonthView月視圖自定義控件,內部構造函數初始化一個Scroller實例用於輔助滑動,一個縮放動畫監聽(單選或者多選模式下,選中某一項會有個縮放動畫的圓圈背景)

   public MonthView(Context context) {
        super(context);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            scaleAnimationListener = new ScaleAnimationListener();
        }
        mScroller = new Scroller(context);
        mPaint.setTextAlign(Paint.Align.CENTER);
    }

computeScroll:主要功能是計算拖動的位移量更新UI,重寫computeScroll()的原因,調用startScroll()是不會有滾動效果的,只有在computeScroll()獲取滾動情況,參考Scroller計算,做出滾動的響應,computeScroll在父控件執行drawChild時,會調用這個方法.要知道計算有沒有終止,需要通過mScroller.computeScrollOffset()

 @Override
 public void computeScroll() {
     if (mScroller.computeScrollOffset()) {
         scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
         invalidate();
       } else {
            requestLayout()
         }
 }

onTouch方法內判斷滑動事件,如果還在滑動,down時結束滑動,並記錄位置,MOVE時判斷滑動距離,設置滑動方向mSlideMode (內部定義枚舉類型),根據不同的Mode和計算出的距離,調用Scroller.startScroll方法配合computescroll實現滑動視圖。而Touch的up事件如果造成月視圖切換,可能月份年份的變化,這時候會調用到computeDate方法,該方法判斷後根據條件執行回調,而回調函數在DatePicker內部實現了局部更新UI


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mScroller.forceFinished(true);
                mSlideMode = null;
                isNewEvent = true;
                lastPointX = (int) event.getX();
                lastPointY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (isNewEvent) {
                    if (Math.abs(lastPointX - event.getX()) > 100) {
                        mSlideMode = SlideMode.HOR;
                        isNewEvent = false;
                    } else if (Math.abs(lastPointY - event.getY()) > 50) {
                        mSlideMode = SlideMode.VER;
                        isNewEvent = false;
                    }
                }
                if (mSlideMode == SlideMode.HOR) {
                    int totalMoveX = (int) (lastPointX - event.getX()) + lastMoveX;
                    smoothScrollTo(totalMoveX, indexYear * height);
                } else if (mSlideMode == SlideMode.VER) {
                    int totalMoveY = (int) (lastPointY - event.getY()) + lastMoveY;
                    smoothScrollTo(width * indexMonth, totalMoveY);
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mSlideMode == SlideMode.VER) {
                    if (Math.abs(lastPointY - event.getY()) > 25) {
                        if (lastPointY < event.getY()) {
                            if (Math.abs(lastPointY - event.getY()) >= criticalHeight) {
                                indexYear--;
                                centerYear = centerYear - 1;
                            }
                        } else if (lastPointY > event.getY()) {
                            if (Math.abs(lastPointY - event.getY()) >= criticalHeight) {
                                indexYear++;
                                centerYear = centerYear + 1;
                            }
                        }
                        buildRegion();
                        computeDate();
                        smoothScrollTo(width * indexMonth, height * indexYear);
                        lastMoveY = height * indexYear;
                    } else {
                        defineRegion((int) event.getX(), (int) event.getY());
                    }
                } else if (mSlideMode == SlideMode.HOR) {
                    if (Math.abs(lastPointX - event.getX()) > 25) {
                        if (lastPointX > event.getX() &&
                                Math.abs(lastPointX - event.getX()) >= criticalWidth) {
                            indexMonth++;
                            centerMonth = (centerMonth + 1) % 13;
                            if (centerMonth == 0) {
                                centerMonth = 1;
                                centerYear++;
                            }
                        } else if (lastPointX < event.getX() &&
                                Math.abs(lastPointX - event.getX()) >= criticalWidth) {
                            indexMonth--;
                            centerMonth = (centerMonth - 1) % 12;
                            if (centerMonth == 0) {
                                centerMonth = 12;
                                centerYear--;
                            }
                        }
                        buildRegion();
                        computeDate();
                        smoothScrollTo(width * indexMonth, indexYear * height);
                        lastMoveX = width * indexMonth;
                    } else {
                        defineRegion((int) event.getX(), (int) event.getY());
                    }
                } else {
                    defineRegion((int) event.getX(), (int) event.getY());
                }
                break;
        }
        return true;
    }

至於上面的defineRegion方法我也只了解一點(計算真心看不懂),基於v11版本區分單選模式和多選模式,執行縮放動畫,加減速差值器效果(圓圈背景縮放),動畫結束執行對應的回調函數,至於多選的值緩存方式使用的是List,個人認為還有更好的方案,具體參考AbsListView(看了別打我~~(>_<)~~)

  private List dateSelected = new ArrayList<>();

至於圓形背景怎麼來,以前博客提過很多次,不再逼逼叨叨了

小結

Aige就是Aige,看完這個庫收獲良多!今後要加強算法方面的學習了,閏年判斷都差不多忘了,時光飛逝,遙想當年學java,一個閏年判斷的java的demo分分鐘的事,現在還得百度一下!!!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved