过程分析
安卓机顶盒开发中,很多时候都需要我们能够有自定义
View
的能力。而且通常需要处理按键相关的逻辑,其实就是和焦点相关的逻辑。我这里说的焦点并非之前的博文中提到的焦点。前面说到的焦点是一个被标记的View
。我这里说的是视觉上的焦点,就是你能够看到的被标记的位置。通常我们处理焦点问题,可以采用安卓提供的焦点相关的api
。当然我们也可以自己模拟实现焦点。本文将带你实现一个较为简单的模拟焦点的自定义View
。在720p分辨率的安卓盒子上运行效果如下:
代码实现
- 这里贴一下主要的类:
package com.ajay.tvdevelopdemo.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import java.util.List;
/**
* 该组件模拟view内部视觉焦点移动
*
* @author AjayNiu
* @since 2016-11-14
*/
public class EpisodeChooseLayout extends View {
public static final String TAG = "EpisodeChooseLayout";
public static final boolean DEBUG = false;
public static final int TEXT_SIZE = 20;
public static final String FOCUS_COLOR = "#005DBE";
public static final String UNFOCUS_COLOR = "#B3B5BB";
public static final String UNFOCUS_HASSEEN_COLOR = "#898989";
public static final String FOCUS_TEXT_COLOR = "#FFFFFF";
public static final String UNFOCUS_TEXT_COLOR = "#000000";
private static final int SCROLL_TYPE_EDGE = 0;
private static final int SCROLL_TYPE_MIDDLE = 1;
private int mScrollType = SCROLL_TYPE_EDGE;
private int mTextSize = TEXT_SIZE;
private int mFocusItemColor;
private int mUnFocusItemColor;
private int mUnFocusHasSeenItemColor;
private int mFocusTextColor;
private int mUnFocusTextColor;
// 模拟焦点距控件边缘的距离
private int focusStayExtra;
// 可见轴线起始绝对坐标
private int mVisibleStartX;
// 可见轴线结束绝对坐标
private int mVisibleEndX;
// 上次滚动位置
private int lastScrollPosition;
// 实际绘制的条目数据集合
private List<EpisodeChooseItem> mDrawingItems;
private Paint mPaint = new Paint();
private Paint mTextPaint = new Paint();
private Rect focusUpRect = new Rect();
private Rect unFocusUpRect = new Rect();
private Rect mVisibleRect = new Rect();
// item之间的水平间隔
private static final int GAP = 1;
// 焦点位置
private int mFocusPosition;
// 是否得到焦点
private boolean isGainFocus;
// 已观看的最大位置
private int mHasSeenMaxPosition;
// 文本区域
private Rect mTextRect = new Rect();
public EpisodeChooseLayout(Context context) {
super(context);
init();
}
public EpisodeChooseLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setFocusable(true);
mPaint.setAntiAlias(true);
mTextPaint.setAntiAlias(true);
mFocusItemColor = Color.parseColor(FOCUS_COLOR);
mUnFocusItemColor = Color.parseColor(UNFOCUS_COLOR);
mUnFocusHasSeenItemColor = Color.parseColor(UNFOCUS_HASSEEN_COLOR);
mFocusTextColor = Color.parseColor(FOCUS_TEXT_COLOR);
mUnFocusTextColor = Color.parseColor(UNFOCUS_TEXT_COLOR);
}
public void initItems(List<EpisodeChooseItem> mInitItems) {
if (mInitItems.size() <= 0) {
return;
}
int left = 0;
for (int i = 0; i < mInitItems.size(); i++) {
EpisodeChooseItem item = mInitItems.get(i);
item.position = mInitItems.indexOf(item);
item.rect.left = left;
item.rect.top = 0;
item.rect.right = item.rect.left + item.getWidth();
item.rect.bottom = item.getHeight();
left += item.getWidth();
}
this.mDrawingItems = mInitItems;
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (gainFocus) {
isGainFocus = true;
invalidate();
} else {
isGainFocus = false;
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mVisibleStartX = focusStayExtra;
mVisibleEndX = getMeasuredWidth() - focusStayExtra;
mVisibleRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
}
public void updateHasSeenMaxPosition(int mHasSeenMaxPosition) {
this.mHasSeenMaxPosition = mHasSeenMaxPosition;
invalidate();
}
public void setFocusPosition(int mFocusPosition) {
this.mFocusPosition = mFocusPosition;
invalidate();
}
public void setFocusStayExtra(int focusStayExtra) {
this.focusStayExtra = focusStayExtra;
invalidate();
}
private void setScrollType(int type) {
mScrollType = type;
}
public void setTextSize(int mTextSize) {
this.mTextSize = mTextSize;
}
public void setmFocusItemColor(int mFocusItemColor) {
this.mFocusItemColor = mFocusItemColor;
}
public void setmUnFocusItemColor(int mUnFocusItemColor) {
this.mUnFocusItemColor = mUnFocusItemColor;
}
public void setmUnFocusHasSeenItemColor(int mUnFocusHasSeenItemColor) {
this.mUnFocusHasSeenItemColor = mUnFocusHasSeenItemColor;
}
public void setmFocusTextColor(int mFocusTextColor) {
this.mFocusTextColor = mFocusTextColor;
}
public void setmUnFocusTextColor(int mUnFocusTextColor) {
this.mUnFocusTextColor = mUnFocusTextColor;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mTextPaint.setTextSize(mTextSize);
canvas.save();
canvas.clipRect(mVisibleRect);
int translatePosition = 0;
if (mScrollType == SCROLL_TYPE_EDGE) {
translatePosition = adjustPositionOnScreen(mFocusPosition);
} else if (mScrollType == SCROLL_TYPE_MIDDLE) {
translatePosition = adjustPositionMiddle(mFocusPosition);
}
canvas.translate(translatePosition, 0);
for (int i = 0; i < mDrawingItems.size(); i++) {
if (i == mFocusPosition) {
Rect rect = mDrawingItems.get(i).rect;
// draw focus up rect
if (isGainFocus) {
mPaint.setColor(mFocusItemColor);
} else {
if (i <= mHasSeenMaxPosition) {
mPaint.setColor(mUnFocusHasSeenItemColor);
} else {
mPaint.setColor(mUnFocusItemColor);
}
}
focusUpRect.set(rect.left, rect.top, rect.right - GAP, rect.bottom);
canvas.drawRect(focusUpRect, mPaint);
if (isGainFocus) {
mTextPaint.setColor(mFocusTextColor);
} else {
mTextPaint.setColor(mUnFocusTextColor);
}
// draw message
String text = mDrawingItems.get(i).getMessage();
mTextPaint.getTextBounds(text, 0, text.length(), mTextRect);
int x = mDrawingItems.get(i).rect.left + mDrawingItems.get(i).getWidth() / 2 - mTextRect.width() / 2;
int y = mDrawingItems.get(i).rect.top + mDrawingItems.get(i).getHeight() / 2 + mTextRect.height() / 2;
canvas.drawText(mDrawingItems.get(i).getMessage(), x, y, mTextPaint);
} else {
Rect rect = mDrawingItems.get(i).rect;
// draw focus up rect
if (i <= mHasSeenMaxPosition) {
mPaint.setColor(mUnFocusHasSeenItemColor);
} else {
mPaint.setColor(mUnFocusItemColor);
}
unFocusUpRect.set(rect.left, rect.top, rect.right - GAP, rect.bottom);
canvas.drawRect(unFocusUpRect, mPaint);
mTextPaint.setColor(mUnFocusTextColor);
// draw message
String text = mDrawingItems.get(i).getMessage();
mTextPaint.getTextBounds(text, 0, text.length(), mTextRect);
int x = mDrawingItems.get(i).rect.left + mDrawingItems.get(i).getWidth() / 2 - mTextRect.width() / 2;
int y = mDrawingItems.get(i).rect.top + mDrawingItems.get(i).getHeight() / 2 + mTextRect.height() / 2;
canvas.drawText(mDrawingItems.get(i).getMessage(), x, y, mTextPaint);
}
}
canvas.restore();
}
private int adjustPositionMiddle(int focusPosition) {
if (focusPosition >= 0 && focusPosition < mDrawingItems.size()) {
int left = mDrawingItems.get(focusPosition).rect.left;
int width = mDrawingItems.get(focusPosition).getWidth();
int centerX = left + width / 2;
int lastPositionRight = mDrawingItems.get(mDrawingItems.size() - 1).rect.right;
if (lastPositionRight > getMeasuredWidth()) {
if (centerX > getMeasuredWidth() / 2 && centerX < (lastPositionRight - getMeasuredWidth() / 2)) {
return getMeasuredWidth() / 2 - centerX;
} else if (centerX > (lastPositionRight - getMeasuredWidth() / 2)) {
return getMeasuredWidth() - lastPositionRight;
}
}
}
return 0;
}
private int adjustPositionOnScreen(int focusPosition) {
if (focusPosition >= 0 && focusPosition < mDrawingItems.size()) {
int focusLeft = mDrawingItems.get(focusPosition).rect.left;
int focusRight = mDrawingItems.get(focusPosition).rect.right;
debugLog("adjustPositionOnScreen focusLeft : " + focusLeft + " mVisibleStartX : " + mVisibleStartX);
debugLog("adjustPositionOnScreen focusRight : " + focusRight + " mVisibleEndX : " + mVisibleEndX);
debugLog("adjustPositionOnScreen extra : " + (mVisibleEndX - mVisibleStartX));
if (focusLeft < mVisibleStartX) {
mVisibleStartX = focusLeft;
mVisibleEndX = focusLeft + getMeasuredWidth() - focusStayExtra * 2;
if (mFocusPosition == 0) {
lastScrollPosition = -mVisibleStartX;
} else {
lastScrollPosition = -(mVisibleStartX - focusStayExtra);
}
return lastScrollPosition;
} else if (focusRight > mVisibleEndX) {
mVisibleEndX = focusRight;
mVisibleStartX = focusRight - (getMeasuredWidth() - focusStayExtra * 2);
if (mFocusPosition == mDrawingItems.size() - 1) {
lastScrollPosition = -(mVisibleStartX - focusStayExtra * 2);
} else {
lastScrollPosition = -(mVisibleStartX - focusStayExtra);
}
return lastScrollPosition;
} else {
return lastScrollPosition;
}
}
return 0;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (mFocusPosition > 0) {
mFocusPosition--;
invalidate();
return true;
}
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (mFocusPosition < mDrawingItems.size() - 1) {
mFocusPosition++;
invalidate();
return true;
}
} else if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
if (onLayoutClickListener != null) {
onLayoutClickListener.onLayoutClick(mFocusPosition);
}
}
return super.onKeyDown(keyCode, event);
}
public interface OnLayoutClickListener {
void onLayoutClick(int position);
}
OnLayoutClickListener onLayoutClickListener;
public void setOnLayoutClickListener(OnLayoutClickListener onLayoutClickListener) {
this.onLayoutClickListener = onLayoutClickListener;
}
private void debugLog(String log) {
if (DEBUG) {
Log.d(TAG, log);
}
}
}