android组合控件的焦点,撸一个简单的TV版焦点控制的日历控件

1、效果

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

a8bbd9e9f836

效果图.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使得整体偏左,这点可以开下开发者模式显示布局边界试下;

垂直方向:

一个起步+一个水平间距+两个文字大小

这样子,画背景图就猫画虎了,都是计算的东西,就不详细讲,自己体会下

a8bbd9e9f836

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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值