美文网首页
撸一个简单的TV版焦点控制的日历控件

撸一个简单的TV版焦点控制的日历控件

作者: Dane_404 | 来源:发表于2019-03-20 08:37 被阅读0次

    1、效果

    最近需求要一个遥控控制的日历控件,找了半天没找到轮子,就自己撸一个,先看效果图:


    效果图.gif

    2、XML属性,所有属性默认为效果图

    • calender_textSize:星期和日期的字体大小;
    • calender_textColor:日期的字体颜色;
    • calender_currentDayColor:当天日期的颜色;
    • calender_focusDrawable:有焦点的日期的背景图,即日期遥控选中效果;
    • calender_startAndEndPadding:左右的起步距离;
    • calender_horizontalInterval:水平间距的大小;
    • calender_verticalInterval:垂直间距的大小;
    • calender_weekTextColor: 星期的字体颜色;
    • calender_headBg:头部的背景图(选择器有焦点和无焦点效果);
    • calender_headTextSize:头部日期字体大小;
    • calender_headTextColor:头部日期字体颜色;
    • calender_focusTextColor:有焦点的日期的字体颜色,即日期遥控选中效果;

    3、抱着学习心态撸这个控件,写这篇的目的是记录分享下这个TvCalenderView

    先讲个大概,TvCalenderView继承LinearLayout垂直排序,三个子View,先后为HeadView,WeekView,MonthView,就贴一些关键点。

    • MonthView:
      先看下onDraw,关键地方写了注释:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mTempDay = mDay;  //临时变量记录当前的焦点日期
            for (int day = 0; day < mDaysCountOfMonth; day++) {
                /**
                 *  mFirstDayOfWeek 这月第一天是星期几,比如,效果图第一天是周二,mFirstDayOfWeek为3,
                 *  因为是从1开始,周二就是3,那么,按照下面的公式,算错来的row为0,column为2,两者都是
                 *  重0开始,1号的位置就是(0,2)
                 */
                int row = (day + mFirstDayOfWeek - 1)/7;
                int column = (day+mFirstDayOfWeek - 1) % 7;
                mDayStr[row][column] = day+1;   //记录每个月日期的行列位置
                String dayStr = String.valueOf(day+1);
                if (day == 0 && isChangeMonth){  //初始化焦点位置用,默认切换月份1号有焦点
                    isChangeMonth = false;
                    mCurRow = row;   //一开始在0行
                    mCurColumn = column;  //一开始在0列
                }
                if (day+1 == mDay){
                    if (mHasFocus){   //是选中日期,且有焦点
                        mTextPaint.setColor(mTextColor);
                        Rect rect = new Rect();
                        mTextPaint.getTextBounds(dayStr, 0, dayStr.length(), rect);
                        int height = rect.height();//文字高
                        //水平位置:起步+列数*列间距+文字大小*列间距+文字的一半-图片的一半
                        //垂直位置:起步+行数*水平间距+文字大小*(行数+1)- 文字高度/2 - 图片的一半
                        //说明一下,垂直位置,文字大小*(行数+1),+1是因为drawText传入的位置是左下角
                        //drawBitmap传入的位置的图片中心,所以要-文字高度/2
                        canvas.drawBitmap(mBitmap,padding+column*verticalInterval+textSize*column+ mTextPaint.measureText("10") / 2-mBitmap.getWidth()/2,
                                startTop+row * horizontalInterval + textSize * (row + 1) - height / 2-mBitmap.getHeight()/2,mFocusBgPaint);
                        canvas.drawText(dayStr, padding + mTextPaint.measureText("10")/2+column * verticalInterval +textSize*column - mTextPaint.measureText(dayStr) / 2,
                                startTop+ row * horizontalInterval + textSize * (row + 1), mFocusTextPaint);
                    }else {
                        drawDay(canvas, day, row, column, dayStr);
                    }
                }else {
                    drawDay(canvas, day, row, column, dayStr);
                }
            }
        }
      
        private void drawDay(Canvas canvas, int day, int row, int column, String dayStr) {
            mTextPaint.setColor(day+1 == mCurrDay && mYear == mCurrYear && mMonth == mCurrMonth ? mCurrentDayColor : mTextColor);
            canvas.drawText(dayStr, padding + mTextPaint.measureText("10")/2+column * verticalInterval +textSize*column - mTextPaint.measureText(dayStr) / 2,
                    startTop+row * horizontalInterval + textSize * (row + 1), mTextPaint);
        }  
      

      图片讲解一波:
      例如日期8:
      8的位置的第1行,第2列(都是0开始),那么画8这个字:
      水平方向:
      一个起步+两个垂直距离+两个文字大小,为什么是两个不是三个,因为drawText左下角开始画,再-mTextPaint.measureText(dayStr) / 2,举个例子,1和10,两个默认画出来是左对齐的,这就不美观了,要居中对齐,所以就大家都减去文字宽度的一半,这样就居中对齐了,最后再加 mTextPaint.measureText("10")/2,这一步主要是要整体居中,因为前面--mTextPaint.measureText(dayStr) / 2使得整体偏左,这点可以开下开发者模式显示布局边界试下;
      垂直方向:
      一个起步+一个水平间距+两个文字大小
      这样子,画背景图就猫画虎了,都是计算的东西,就不详细讲,自己体会下


      8.png

    接下来看onkeyDown:
    处理遥控器的事件:
    mCurRow和mCurColumn在onDraw拿到1号的位置,然后根据遥控事件拿到对应的日期,再invalidate重新绘制:

      //遥控事件
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_UP:
                    if (mCurRow <= 0) {   //如果是在第0行,再按上键的话,这时MonthView会失去焦点
                        return super.onKeyDown(keyCode, event);
                    }
                    mCurRow--;
                    mDay = mDayStr[mCurRow][mCurColumn]; //拿出存的day,后面调用invalidate重新绘制
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    if (mCurRow >= mDayStr.length - 1) {  //下限
                        return super.onKeyDown(keyCode, event);
                    }
                    mCurRow++;
                    mDay = mDayStr[mCurRow][mCurColumn];
                    if (mDay == 0) {  //拿不到的情况,比如效果图25号下面没有日期
                        mDay = mTempDay;  //mTempDay记录了上一次的日期
                        mCurRow--;   //上面加了拿不到,减回去
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if (mCurColumn <= 0) {
                        preMonth();     //往左到界限,跳到上一个月
                        return super.onKeyDown(keyCode, event);
                    }
                    mCurColumn--;
                    mDay = mDayStr[mCurRow][mCurColumn];
                    if (mDay == 0) {   //拿不到的情况,比如效果图1号左边没有日期
                        preMonth();
                        return super.onKeyDown(keyCode, event);
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (mCurColumn >= mDayStr[0].length - 1) {
                        nextMonth();  //往右到界限,跳到下一个月
                        return super.onKeyDown(keyCode, event);
                    }
                    mCurColumn++;
                    mDay = mDayStr[mCurRow][mCurColumn];
                    if (mDay == 0) {   //拿不到的情况,比如效果图31号右边没有日期
                        nextMonth();
                        return super.onKeyDown(keyCode, event);
                    }
                    break;
                case KeyEvent.KEYCODE_ENTER:  //确认按钮
                    if (mOnDateSeletedListener != null) {
                        mOnDateSeletedListener.onDateSelected(mYear, mMonth, mDay);
                    }
                    return super.onKeyDown(keyCode, event);
                default:
                    return super.onKeyDown(keyCode, event);
            }
            invalidate();
            return true;
        }
    
        public void nextMonth() {
            if (mMonth >= 11) {
                mMonth = 0;
                mYear++;
                setYearAndMonth(mYear, mMonth);  //换月份
                ((TvCalenderView) getParent()).changeHeadData(mMonth, mYear);  //调用改变头部的日期
                return;
            }
            mMonth++;
            setYearAndMonth(mYear, mMonth);
            ((TvCalenderView) getParent()).changeHeadData(mMonth, mYear);
        }
    
    • WeekView不讲了,HeadView就是组合控件,主要帖下遥控事件:

         @Override
            public boolean onKeyDown(int keyCode, KeyEvent event) {
                switch (keyCode){
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        mIvPre.setImageResource(R.drawable.date_last_focused);
                        ((TvCalenderView) getParent()).preMonth();  //按左刷新Month
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                mIvPre.setImageResource(R.drawable.date_last_normal);   //伪装点击效果
                            }
                        },100);
                        break;
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        mIvNext.setImageResource(R.drawable.date_next_focused);
                        ((TvCalenderView) getParent()).nextMonth();
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                mIvNext.setImageResource(R.drawable.date_next_normal);
                            }
                        },100);
                        break;
                }
                return super.onKeyDown(keyCode, event);
            }
      

    最后

    贴上Demo地址:
    https://github.com/CzdCoder/TvCalenderView
    分享下自定义View学习系列:
    Android 开发进阶: 自定义 View 1-1 绘制基础:http://hencoder.com/ui-1-1/
    Android 开发进阶: 自定义 View 1-2 Paint 详解:http://hencoder.com/ui-1-2/
    Android 开发进阶:自定义 View 1-3 文字的绘制:http://hencoder.com/ui-1-3/
    Android 开发进阶:自定义 View 1-4 Canvas 对绘制的辅助:http://hencoder.com/ui-1-4/
    Android 开发进阶:自定义 View 1-5 绘制顺序:http://hencoder.com/ui-1-5/

    相关文章

      网友评论

          本文标题:撸一个简单的TV版焦点控制的日历控件

          本文链接:https://www.haomeiwen.com/subject/vsaomqtx.html