Androidで高カスタマイズカレンダーコントロールを実現

19521 ワード

Androidで高カスタマイズカレンダーコントロールを実現
このコントロールはGitHub上のカレンダー項目に基づいて、高度にカスタマイズされた修正版です.そのため、元の項目の住所を添付します.https://github.com/SundeepK/CompactCalendarView
  • 既存のコントロールに基づいてヘッダーを追加する月表示
  • は、データの日付に応じて異なるスタイルを表示する
  • を追加する.
  • データ日付によるイベント
  • を追加する.
  • クリック外部非表示カレンダー効果
  • を追加
  • クリックイベント
  • を追加
  • Android高カスタマイズカレンダーコントロールを実現

  • 概要
  • カスタムヘッダの実装
  • ヘッドビュー
  • を追加
  • ヘッダクリックイベント
  • を追加
  • カレンダコントロールにデータを追加データに基づいて表示スタイルを変更
  • コントロールにデータを追加
  • データ制御カレンダー関連表示
  • クリックイベント
  • を追加
  • 補足説明
  • コントロールアニメーションに関する質問
  • 初期隠蔽に関する問題
  • クリックコントロール以外の場所でカレンダーコントロールを隠す実装
  • について
  • CompactCalendarViewの作者
  • に感謝


    概要
    CompactCalendarViewは、カレンダーのデフォルト当日および他の日付を選択する表示、スライドイベント、クリックイベントなどの機能を実現し、非常に完全なオープンソースプロジェクトです.--[プロジェクトアドレス]
    しかし、開発の必要性に介在して、まだ多くの機能が実現されていないので、ここで詳細なカスタマイズの需要を説明して、あなたを助けることができることを望んでいます.
    文章コントロールの原型はCompactCalendarViewを使用し、多くの使いやすい機能を拡張しています.元のコントロール機能の使用方法は、Githubを参照してください.
    カスタムヘッダの実装
    CompactCalendarView:プロジェクトを開くとパッケージ化されたviewであることがわかります具体的な操作はCompactCalendarControllerクラスに渡されました
    主な方法は次のとおりです.
    void drawMonth(Canvas canvas, Calendar monthToDrawCalender, int offset)
    このメソッドの最初のパラメータは言うまでもなく、分からない場合はカスタムviewのOnDrawメソッドをご自身でご理解ください
    2番目のパラメータは、特定の日付を判断するために使用されます.
    3番目のパラメータはオフセット量を表すために使用されます(このオフセット量はスライドイベントのオフセット量を指します).
    主なロジック:
     for (int dayColumn = 0, dayRow = 0; dayColumn <= 6; dayRow++) {
                if (dayRow == 7) {
                    dayRow = 0;
                    if (dayColumn <= 6) {
                        dayColumn++;
                    }
                }
                if (dayColumn == dayColumnNames.length) {
                    break;
                }
                float xPosition = widthPerDay * dayColumn + paddingWidth + paddingLeft + accumulatedScrollOffset.x + offset - paddingRight;
                float yPosition = dayRow * heightPerDay + paddingHeight + headHeight;
                if (xPosition >= growFactor && (isAnimatingWithExpose || animationStatus == ANIMATE_INDICATORS) || yPosition >= growFactor) {
                    continue;
                }
                if (dayRow == 0) {
                    if (shouldDrawDaysHeader) {
                        dayPaint.setColor(calenderTextColor);
                        dayPaint.setTypeface(Typeface.DEFAULT_BOLD);
                        dayPaint.setStyle(Paint.Style.FILL);
                        dayPaint.setColor(calenderTextColor);
                        canvas.drawText(dayColumnNames[dayColumn], xPosition, paddingHeight, dayPaint);
                        dayPaint.setTypeface(Typeface.DEFAULT);
                    }
                }

    上記のコードから明らかなように、これは7行7列のマトリクスを描き、最初の行は曜日を表示します.
    スペースにヘッドビューを追加するにはここで文章を書かなければなりません
    ヘッドビューの追加
        void drawHead(Canvas canvas, Calendar yearToMonthCalender, int offset) {
            int year = yearToMonthCalender.get(Calendar.YEAR);    //   
            int month = yearToMonthCalender.get(Calendar.MONTH) + 1;   //    ,0  1  
            dayRect.setColor(Color.argb(255, 66, 66, 66));
            dayRect.setStyle(Paint.Style.FILL);
            dayRect.setTextSize(textSize + 12);
            String text = year + " " + month + " ";
            float textWidth = dayRect.measureText(text);
            //                    offset (   )                
            if (width * -monthsScrolledSoFar == offset) {
                Rect lastRect = new Rect((int)(widthPerDay * 2 + paddingWidth + paddingLeft  - paddingRight - lastMonthIcon.getWidth())
                        ,textSize/2
                        ,(int)(widthPerDay * 2 + paddingWidth + paddingLeft  - paddingRight)
                        , textSize+paddingHeight/2);
    
    
                Rect nextRect = new Rect((int)(widthPerDay * 4 + paddingWidth + paddingLeft  - paddingRight)
                        ,textSize/2
                        ,(int)(widthPerDay * 4 + paddingWidth + paddingLeft  - paddingRight+nextMonthIcon.getWidth())
                        ,textSize+paddingHeight/2);
    
                canvas.drawText(text, widthPerDay * 7 / 2 - textWidth / 2, paddingHeight, dayRect);
                canvas.drawBitmap(nextMonthIcon, null , nextRect, null);
                canvas.drawBitmap(lastMonthIcon, null, lastRect, null);
            }
    
        }

    ここでは簡単に頭を描き、現実の年月のテキストと月を切り替えるためのボタンを2つ描き、drawMonthメソッドにこのメソッドを配置し、頭の位置を予約して具体的なコードを以下のように変更します.
      void drawMonth(Canvas canvas, Calendar monthToDrawCalender, int offset) {
          ...
          drawHead(canvas, monthToDrawCalender, offset);//           
          ...
          //             headHeight             
          for (int dayColumn = 0, dayRow = 0; dayColumn <= 6; dayRow++) {
                if (dayRow == 7) {
                    dayRow = 0;
                    if (dayColumn <= 6) {
                        dayColumn++;
                    }
                }
                if (dayColumn == dayColumnNames.length) {
                    break;
                }
                float xPosition = widthPerDay * dayColumn + paddingWidth + paddingLeft + accumulatedScrollOffset.x + offset - paddingRight;
                float yPosition = dayRow * heightPerDay + paddingHeight + headHeight;
                if (xPosition >= growFactor && (isAnimatingWithExpose || animationStatus == ANIMATE_INDICATORS) || yPosition >= growFactor) {
    
                    continue;
                }
                if (dayRow == 0) {
    
                    if (shouldDrawDaysHeader) {
                        dayPaint.setColor(calenderTextColor);
                        dayPaint.setTypeface(Typeface.DEFAULT_BOLD);
                        dayPaint.setStyle(Paint.Style.FILL);
                        dayPaint.setColor(calenderTextColor);
                        canvas.drawText(dayColumnNames[dayColumn], xPosition, paddingHeight + headHeight, dayPaint);
                        dayPaint.setTypeface(Typeface.DEFAULT);
                    }
                }
    
    

    これにより、私たちの頭部がビューに表示され、このコントロールは全体的に1つの頭部の距離を下に移動することに相当し、すべてのクリックイベントが錯乱するので、私たちの対応するクリックイベントを追加するついでに、既存のクリックイベントを校正します.
    ヘッドクリックイベントの追加
    まず自分で追加した頭部のクリックイベントを書いて、コードは以下の通りです.
         boolean onIconTouch(MotionEvent event){
            int x = Math.round((paddingLeft + event.getX() - paddingWidth - paddingRight) / widthPerDay);
            int y = Math.round((event.getY()));
            if (x ==2
                    && y < headHeight+paddingHeight
                    && y > 0) {
                scrollPreviousMonth();
                return true;
            } else if (x == 4
                    && y < headHeight+paddingHeight
                    && y > 0) {
                scrollNextMonth();
                return true;
            }
            return false;
        }

    私は左右の2つのボタンを3列目と5列目の位置に配置しました.自分のニーズに合わなければ、自分で修正することができます.その後、私たちのクリックイベントを既存のクリックイベントに追加し、クリックイベントの乱れ問題を修正します.
        void onSingleTapUp(MotionEvent e) {
            // Don't handle single tap when calendar is scrolling and is not stationary
            if (isScrolling()) {
                return;
            }
            //      
            if (onIconTouch(e)){
                return;
            }
    
            int dayColumn = Math.round((paddingLeft + e.getX() - paddingWidth - paddingRight) / widthPerDay);
            //                         
            int dayRow = Math.round((e.getY() - paddingHeight - headHeight) / heightPerDay);
    

    ここまでヘッダの追加が完了しました.
    カレンダコントロールにデータを追加し、データに基づいて表示スタイルを変更します.
    私たちはこのような需要があると仮定して、私たちは毎日のデータをローカルに保存して、その日ローカルにデータがあればクリックして相応のデータを取り出すことができて、しかもオプションの日付は黒で、オプションの日は灰色です.このようなニーズは、カレンダーコントロールとデータをバインドする必要があります.では、まずデータから始めましょう.
    コントロールにデータを追加するには、次の手順に従います.
    List<Calendar> list;//      
    //          
    void setDates(List<DateEntry> dates,Context context){
            this.list = new ArrayList<>();
    
            if (dates.size() == 0 || dates.isEmpty()){
                dates = null ;
            }else {
                for (int i = 0; i < dates.size() ; i++) {
                    Calendar c = Calendar.getInstance(timeZone,locale);
                    c.setTime(new Date(dates.get(i).getTime()));
                    this.list.add(c);
                }
            }
            init(context);
        }

    この方法をCompactCalendarViewに公開しました
        public void setDates(List<DateEntry> list , Context context){
            compactCalendarController.setDates(list,context);
        }
    

    これにより、データが入力されると、関連する操作が可能になります.
    データに基づいてカレンダ関連の表示を制御
    それとも描画方法に戻るか、
    //                             ,                  ,         
                    int day = ((dayRow - 1) * 7 + dayColumn + 1) - firstDayOfMonth;
                    int defaultCalenderTextColorToUse = calenderTextColor;
                    if (currentCalender.get(Calendar.DAY_OF_MONTH) == day && isSameMonthAsCurrentCalendar && !isAnimatingWithExpose) {
                        drawDayCircleIndicator(currentSelectedDayIndicatorStyle, canvas, xPosition, yPosition, currentSelectedDayBackgroundColor);
                        defaultCalenderTextColorToUse = Color.WHITE;
                    } else if (isSameYearAsToday && isSameMonthAsToday && todayDayOfMonth == day && !isAnimatingWithExpose) {
                        drawDayCircleIndicator(currentDayIndicatorStyle, canvas, xPosition, yPosition, currentDayBackgroundColor);
                        defaultCalenderTextColorToUse = currentDayTextColor;
                    }

    ここで筆者は現在表示されている月のみについて操作するので、ここでelse ifコードを追加すると以下のようになります.
                    } else if (list == null || list.isEmpty()) {
                        //      ,      
                        defaultCalenderTextColorToUse = Color.argb(255,189,189,189);
                    } else {
                        //     ,          ,            
                        for (int i = 0; i < list.size(); i++) {
                            Calendar c = list.get(i);
                            if (c.get(Calendar.MONTH) == monthToDrawCalender.get(Calendar.MONTH)
                                    && c.get(Calendar.DAY_OF_MONTH) == day) {
                                defaultCalenderTextColorToUse = calenderTextColor;
                                break;
                            } else {
                                defaultCalenderTextColorToUse = Color.argb(255,189,189,189);
                            }
                        }
                    }

    ここではフォントの色を変えたと判断するだけで、集合を巡る方法で対応するデータを探すのは理想的ではありませんが、仕方なく筆者もデータを探す方法が思いつかず、データに上限がある場合にこの方法が実現できるのです.
    クリックイベントを追加
    描き終わったら、クリックイベントができるかどうか、具体的にはクリックイベントの方法に戻ります.
             //      
              boolean canSelect = false;
              //               
                if (list == null || list.isEmpty()) {
                    canSelect = false;
                } else {
                    for (int i = 0; i < list.size(); i++) {
                        Calendar c = list.get(i);
                        if (c.get(Calendar.MONTH) == calendarWithFirstDayOfMonth.get(Calendar.MONTH)
                                && (c.get(Calendar.DAY_OF_MONTH)-1) == dayOfMonth) {
                            canSelect = true;
                            break;
                        }
                    }
                }
                //          
                if (canSelect) {
                    calendarWithFirstDayOfMonth.add(Calendar.DATE, dayOfMonth);
    
                    currentCalender.setTimeInMillis(calendarWithFirstDayOfMonth.getTimeInMillis());
                    performOnDayClickCallback(currentCalender.getTime());
                }
    

    これで、結合データ部分が完了します.
    補足説明
    コントロールアニメーションについて
    元のコントロールでは、表示と非表示の両方のアニメーションが実際に親コントロールのサイズを変更するため、ここでの非表示はVisibilityのパラメータを変更することによって行われません.
    初期非表示の問題について
    上記の状況により、コントロールを初期非表示にするには、親コントロールの高さを0に設定するだけでよく、非ストレッチの展開を使用する場合は、親コントロールの幅も0に設定する必要があります.
    コントロール以外の場所をクリックしてカレンダーコントロールを隠す実装について
    開発時間の関係上、このクリックはコントロールにカプセル化されていませんが、実際にクリック以外の場所はonTouchメソッドで現在クリックしているviewがカレンダーコントロールではないということです.
        public boolean onTouch(View v, MotionEvent event) {
            if (v instanceof CompactCalendarView) {
    
            } else {
                if (shouldShow) {
                    if (!compactCalendarView.isAnimating()) {
                        compactCalendarView.hideCalendar();
                        shouldShow = false;
                    }
                }
            }
            return false;
        }

    CompactCalendarViewの作者に感謝します.