导读:TV相关的资料网上相对来说少点,我早期写了七篇TV相关的开发总结,有开源了一些Demo,在我的github上,今天是单灿灿同学独家在本公众平台发布他最新开源的TV框架。单灿灿的blog地址是:http://blog.csdn.net/zhcswlp0625/。
从初TV开发到现在,在移动边框上用过很多方法。
下面我来简单的列出来使用过那些解决方法和思路:-
1,在所有需要放大和设置边框的View下方嵌套一层FrameLayout,作为放大的背景的容器。焦点移动上去,算出当前View的大小,然后再设置FrameLayout的大小与.9图片并bringtoFront();
-
2,为每个需要放大与突出的View设置shape和selector,这个是我最推荐的方法,现在很多TV的APP都采用这种,但是有个缺点,发光和阴影并不能设置。这与需要稍微有点炫酷效果的桌面有点不符合。
-
3,全局FrameLayout,这个是我现在在用的方法,现在已经整理成一套框架,不久就会开源,现在还有示例Demo未完成。
红圈所标出来的是几个主要的类与自定义View,下面我们来深入(我在设计的时候,焦点处理是各自处理各自的,解耦)。
先上两幅比较难的界面(重点在于焦点的处理与动画的处理,图一有动态的添加和删除)。
public interface MoveAnimationHelper { void drawMoveView(Canvas canvas);//绘制MoveView //放大缩小函数 void setFocusView(View currentView, View oldView, float scale); // 边框移动函数 void rectMoveAnimation(View currentView, float scaleX, float scaleY); MoveFrameLayout getMoveView(); //边框view void setMoveView(MoveFrameLayout moveView);//setMoveView void setTranDurAnimTime(int time);//设置移动时间 //是否凸出显示 void setDrawUpRectEnabled(boolean isDrawUpRect); }MoveFrameLayout是全局的移动飞框,就像文章开头的1的实现类似,但是全局只有一个。
最主要的绘制函数就是 MoveFrameLayout这个类了,这个类就是我们的边框移动 View,这个 View 主要实现边框的生成与移动,还有阴影的添加
public class MoveFrameLayout extends FrameLayout { private static final String TAG = "MoveFramLayout"; private Context mContext; private Drawable mRectUpDrawable; private Drawable mRectUpShade; private MoveAnimationHelper mMoveAnimationHelper; private RectF mShadowPaddingRect = new RectF(); private RectF mUpPaddingRect = new RectF(); public MoveFrameLayout(Context context) { super(context); init(context); } public MoveFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MoveFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { mContext = context; //必须要设置,如果我们想要重写onDraw,就要调用setWillNotDraw(false) setWillNotDraw(false); //动画的实现类,接下来就要讲解 mMoveAnimationHelper = new MoveAnimationHelperImplement(); mMoveAnimationHelper.setMoveView(this); } /*下面的方法基本是调用MoveAnimationHelperImplement的实现方法, 来进行我们的放大缩小以及其他展示*/ public void setFocusView(View currentView, View oldView, float scale) { mMoveAnimationHelper.setFocusView(currentView, oldView, scale); } public View getUpView() { return this; } @Override protected void onDraw(Canvas canvas) { if (mMoveAnimationHelper != null) { mMoveAnimationHelper.drawMoveView(canvas); return; } super.onDraw(canvas); } public void setUpRectResource(int id) { try { // 移动的边框. this.mRectUpDrawable = mContext.getResources().getDrawable(id); invalidate(); } catch (Exception e) { e.printStackTrace(); } } public void setUpRectShadeResource(int id) { // 移动的边框. this.mRectUpShade = mContext.getResources().getDrawable(id); invalidate(); } public Drawable getShadowDrawable() { return this.mRectUpShade; } public Drawable getUpRectDrawable() { return this.mRectUpDrawable; } public RectF getDrawShadowRect() { return this.mShadowPaddingRect; } public RectF getDrawUpRect() { return this.mUpPaddingRect; } public void setUpPaddingRect(RectF upPaddingRect) { mUpPaddingRect = upPaddingRect; } public void setShadowPaddingRect(RectF shadowPaddingRect) { mShadowPaddingRect = shadowPaddingRect; } public void setTranDurAnimTime(int defaultTranDurAnim) { mMoveAnimationHelper.setTranDurAnimTime(defaultTranDurAnim); } public void setDrawUpRectEnabled(boolean isDrawUpRect) { mMoveAnimationHelper.setDrawUpRectEnabled(isDrawUpRect); } }MoveAnimationHelperImplement,MoveAnimationHelper的实现者。
这是这个类里面最主要的方法setFocusView。
下面是绘制边框和绘制阴影的方法,这次方法中可以动态的调节移动边框的大小,实现全包裹或者是类似于padding的效果。
/** * 绘制最上层的移动边框. */ public void onDrawUpRect(Canvas canvas) { Drawable drawableUp = getMoveView().getUpRectDrawable(); if (drawableUp != null) { //从MoveView()中获取的,你可以自己在activity调节。 RectF paddingRect = getMoveView().getDrawUpRect(); int width = getMoveView().getWidth(); int height = getMoveView().getHeight(); Rect padding = new Rect(); // 边框的绘制. drawableUp.getPadding(padding); drawableUp.setBounds((int) (-padding.left + (paddingRect.left)), (int) (-padding.top + (paddingRect.top)), (int) (width + padding.right - (paddingRect.right)), (int) (height + padding.bottom - (paddingRect.bottom))); drawableUp.draw(canvas); } } /** * 绘制外部阴影. */ public void onDrawShadow(Canvas canvas) { Drawable drawableShadow = getMoveView().getShadowDrawable(); if (drawableShadow != null) { //从MoveView()中获取的,你可以自己在activity调节。 RectF shadowPaddingRect = getMoveView().getDrawShadowRect(); int width = getMoveView().getWidth(); int height = getMoveView().getHeight(); Rect padding = new Rect(); drawableShadow.getPadding(padding); drawableShadow.setBounds((int) (-padding.left + (shadowPaddingRect.left)), (int) (-padding.top + (shadowPaddingRect.top)), (int) (width + padding.right - (shadowPaddingRect.right)), (int) (height + padding.bottom - (shadowPaddingRect.bottom))); drawableShadow.draw(canvas); }
根部局所采用的方法是继承RelativeLayout
最上层的layout ,用来包裹我们所有的控件,这样,主要是为了放大的时候,控件不会被挡住
public class MainRelativeLayout extends RelativeLayout { private int position; public MainRelativeLayout(Context context) { super(context); init(context); } private void init(Context context){ //是否现限制其他控件在它周围绘制选择false setClipChildren(false); //是否限制控件区域在padding里面 setClipToPadding(false); //用于改变控件的绘制顺序 setChildrenDrawingOrderEnabled(true); getViewTreeObserver() .addOnGlobalFocusChangeListener(new ViewTreeObserver. OnGlobalFocusChangeListener() { @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) { position = indexOfChild(newFocus); if (position != -1) { bringChildToFront(newFocus); // 然后让控件重画,这样会好点 newFocus.postInvalidate();。 } } }); } /** * 此函数 dispatchDraw 中调用. * 原理就是和最后一个要绘制的view,交换了位置. * 因为dispatchDraw最后一个绘制的view是在最上层的. * 这样就避免了使用 bringToFront 导致焦点错乱问题. */ @Override protected int getChildDrawingOrder(int childCount, int i) { if (position != -1) { if (i == childCount - 1){ return position; } if (i == position) return childCount - 1; } return i; } }
使用方法两步走:
一,布局文件
public class EntryActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; MainRelativeLayout mRelativeLayout; MoveFrameLayout mMoveView; View mOldFocus; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_entry); mRelativeLayout = (MainRelativeLayout) findViewById(R.id.activity_entry); mMoveView = (MoveFrameLayout) findViewById(R.id.entrymove); mMoveViewsetDetail(); initRelativeLayout(); } private void mMoveViewsetDetail() { //这里也可以设置shape或者是.9图片 mMoveView.setUpRectResource(R.drawable.conner); //调整大小,如果你的边框大了就修改w_或者h_这两个参数 float density = getResources().getDisplayMetrics().density; RectF receF = new RectF(-getDimension(R.dimen.w_5) * density, -getDimension(R.dimen.h_5) * density, -getDimension(R.dimen.w_5) * density, -getDimension(R.dimen.h_5) * density); //重新为mMoveView设置大小 mMoveView.setUpPaddingRect(receF); mMoveView.setTranDurAnimTime(400); } public float getDimension(int id) { return getResources().getDimension(id); } //这是焦点的全局监听方法,与OnFocusChangeListener不同, //这个方法长安不执行。 private void initRelativeLayout() { mRelativeLayout.getViewTreeObserver(). addOnGlobalFocusChangeListener( new ViewTreeObserver. OnGlobalFocusChangeListener() { @Override public void onGlobalFocusChanged( View oldFocus, View newFocus) { if (newFocus != null) { // newFocus.bringToFront(); //设置居于放大的view之上。 mMoveView.setDrawUpRectEnabled(true); float scale = 1.1f; mMoveView.setFocusView(newFocus, mOldFocus, scale); //将mMoveView的位置bringToFront() mMoveView.bringToFront(); //自己将移动后的View进行保存, mOldFocus = newFocus; } } }); } }大功告成了,简单吧?你可以先下载体验一下,也可以关注我,后续提供更多示例,RecyclerView,带有指示器的ViewPager等等。
以上对应源码下载地址:http://pan.baidu.com/s/1gfqWwVp(ps:后期完善里面的坑,开源github,敬请关注)
更多原创TV系列文章,可点击下方文章对应链接:
Android TV开发总结(一)构建一个TV app前要知道的事儿
Android TV开发总结(二)构建一个TV Metro界面(仿泰捷视频TV版)
Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑
Android TV开发总结(四)通过RecycleView构建一个TV app列表页(仿腾讯视频TV版)
Android TV开发总结(五)TV上屏幕适配总结
Android TV开发总结(六)构建一个TV app的直播节目实例
Android TV开发总结(七)构建一个TV app中的剧集列表控件
第一时间获得【不止个人原创 android/音视频技术干货,问题深度总结,FrameWork源码解析,插件化研究,FFmpeg研究,直播技术,最新开源项目推荐,还有更多职场思考】,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码