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

2,381 阅读4分钟

1.前期

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

2.准备

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

3.开干

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

4.遇到问题

就拿一个控件获取焦点时的问题来说吧,别人主流的TV应用里的控件获取焦点显示焦框时,控件里的内容是不是被挤压的。而且有的焦框带有阴影,阴影还会复盖在别的控件之上,也就是说焦点框不占据控件的大小,如果有传统的方式给控件设置src或是background属性来显示焦点框的话是会占据控件原本的大小的。

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

7.焦点知识入门-焦点框问题[00_08_02][20230518-190621-4].JPG

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库