1.前期

上一年公司开展了一个新的项目,是一个运行在机顶盒上的App。项目司理把整个部分的几个重要成员叫到会议室开了场研讨,讨论了关于整个项目的详细情况,可是公司现有做安卓哥们都没有开发过Tv上的项目,所以当时都没有人自动想要担任这个项目的开发。或许在会上我问的问题比较多引起了留意。然后就毫无意外的把这个项目客户端开发硬塞了给我担任。我也是醉了。。。

2.准备

在美工(设计师)正画高保真图的这段时刻我也开端了研讨关于在机顶盒上的一些相关技术储备,也试的写了一些Demo出来。感觉仍是阔以拿捏的。可是当时我比及高保真出来的时侯我们一起探讨机顶盒上的一些交互时,我发现我的的相关技术储备仍是有点欠缺,没办法,只能先跟着图开端做着。

3.开干

没过这方面开发的哥们或许不知道。开发一个几个按钮外加一个列表的页面假如是手机端的我不用半小时就写完了,可是我在开发TV上的类似的页面时我足足做一了一个多星期。并且产司理看了仍是不满意,说这不行,说那焦点有问题。还经常用几个干流的Tv使用在跟我展示说他人也是这么做,也是那么做。

4.遇到问题

就拿一个控件获取焦点时的问题来说吧,他人干流的TV使用里的控件获取焦点显现焦框时,控件里的内容是不是被揉捏的。并且有的焦框带有暗影,暗影还会复盖在别的控件之上,也便是说焦点框不占有控件的巨细,假如有传统的方式给控件设置src或是background特点来显现焦点框的话是会占有控件原本的巨细的。

如图下面三个正方形的控件的宽高都是100dp的,第1个和第2个是能够获取焦点的,第3个是用来作为对比巨细的。给第1,2个控件添加了一个selector类型的drawable,作为当控件获取焦点时的的焦点框,很明显能够看到,当获取焦点时第2个控件显现了一个红色的焦点框,可是焦点框却揉捏了控件的内容,也便是说焦点框显现在控件100dp之内。像这种方式是有问题的。我们要的是焦点框要显现的控件之外的区域这样就会不占用控件的巨细。

初入Android TV/机顶盒应用开发小记1

5寻找解决方案

顺着这个问题在各大社区寻找解决方案,找到了一种能够把焦点框显现在控件之外的方式。中心便是默许情况下Android的组件能够制作超出它原本巨细之外的区域,可是默许只会显现控件巨细之内的区域,假如我把给这个控件的父控件的两个特点设置为false那么就能够进显现控件之外的内容了。而这两个特点便是:

android:clipChildren="false"
android:clipToPadding="false"

接着就要自定义一个控件,这里以ImageView为例,主页要拿到获取焦点框图片的边框巨细:

int resourceId = ta.getResourceId(R.styleable.EBoxImageView_ebox_iv_focused_border,
        -1);
Rect mFocusedDrawable.getPadding(mFocusDrawableRect);

然后计算出焦点框加点组件之后的整个显现的区域的巨细,

private void mergeRect(Rect layoutRect, Rect drawablePaddingRect) {
    layoutRect.left -= drawablePaddingRect.left;
    layoutRect.right += drawablePaddingRect.right;
    layoutRect.top -= drawablePaddingRect.top;
    layoutRect.bottom += drawablePaddingRect.bottom;
}

最终再根据这个巨细区域制作焦点框,而制作内容这一块直接调用super.onDraw(canvas);就能够了。

下面是一个完整的代码:

public class MyImageView extends AppCompatImageView {
    private static final String TAG = "EBoxImageView";
    private static final Rect mLayoutRect = new Rect();
    private static final Rect mFocusDrawableRect = new Rect();
    private final static Drawable DEFAULT_FOCUSED_DRAWABLE = ResourceUtils.getDrawable(R.drawable.drawable_focused_border);
    private Drawable mFocusedDrawable = DEFAULT_FOCUSED_DRAWABLE;
    public MyImageView(Context context) {
        this(context, null);
    }
    public MyImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public MyImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }
    private void init(AttributeSet attributeSet) {
        setScaleType(ScaleType.FIT_XY);
        TypedArray ta = getContext().obtainStyledAttributes(attributeSet, R.styleable.MyImageView);
        boolean selectorMode = ta.getBoolean(R.styleable.MyImageView_ebox_iv_selected_mode,false);
        int resourceId = ta.getResourceId(R.styleable.MyImageView_ebox_iv_focused_border,
                -1);
        ta.recycle();
        if(selectorMode){
            setFocusable(false);
        }else {
            setFocusable(true);
        }
        if (resourceId != -1) {
            mFocusedDrawable = ResourceUtils.getDrawable(resourceId);
        }
        mFocusedDrawable.getPadding(mFocusDrawableRect);
    }
    @Override
    public void invalidateDrawable(@NonNull Drawable dr) {
        super.invalidateDrawable(dr);
        invalidate();
    }
    @CallSuper
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isFocused()||isSelected()) {
            getDrawingRect(mLayoutRect);
            mergeRect(mLayoutRect, mFocusDrawableRect);
            mFocusedDrawable.setBounds(mLayoutRect);
            canvas.save();
            mFocusedDrawable.draw(canvas);
            canvas.restore();
        }
    }
    /**
     * 合并drawable的padding到borderRect里去
     *
     * @param layoutRect          当时布局的Rect
     * @param drawablePaddingRect borderDrawable的Rect
     */
    private void mergeRect(Rect layoutRect, Rect drawablePaddingRect) {
        layoutRect.left -= drawablePaddingRect.left;
        layoutRect.right += drawablePaddingRect.right;
        layoutRect.top -= drawablePaddingRect.top;
        layoutRect.bottom += drawablePaddingRect.bottom;
    }
    /**
     * 指定一个焦点框图片资源,假如不调用此方法默许用,R.drawable.drawable_recommend_focused
     *
     * @param focusDrawableRes
     */
    public void setFocusDrawable(@DrawableRes int focusDrawableRes) {
        mFocusedDrawable = ResourceUtils.getDrawable(focusDrawableRes);
        mFocusedDrawable.getPadding(mFocusDrawableRect);
    }

总结

以上便是在开发AndroidTV、机顶盒中遇到的焦点框问题的解决方案,后来在CS某N社区中找到一套关于AndroidTV项目开发实战的视频教程传送门看了一下还不错,在其它当地也找不更好的资源。再加上项目实现太赶没有那么多的试错时刻成本,然后就报名买了那教程。后来边看边开发,用着这套视频的作者提供的一个UI库,来开发我的项目确定便利快速了很多。现在整套视频教程还没看完,一边学习一边写公司的项目和写博客。