打造一个带有模拟焦点的影视剧集选集组件

过程分析

安卓机顶盒开发中,很多时候都需要我们能够有自定义View的能力。而且通常需要处理按键相关的逻辑,其实就是和焦点相关的逻辑。我这里说的焦点并非之前的博文中提到的焦点。前面说到的焦点是一个被标记的View。我这里说的是视觉上的焦点,就是你能够看到的被标记的位置。通常我们处理焦点问题,可以采用安卓提供的焦点相关的api。当然我们也可以自己模拟实现焦点。本文将带你实现一个较为简单的模拟焦点的自定义View。在720p分辨率的安卓盒子上运行效果如下:

image

代码实现

  • 这里贴一下主要的类:
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);
        }
    }
}

- 完整代码在这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值