美文网首页
两步集成TV移动框架,从未如此简单

两步集成TV移动框架,从未如此简单

作者: 苏州韭菜明 | 来源:发表于2017-05-15 22:56 被阅读138次

    本篇文章已授权微信公众号DriodDeveloper(逆流的鱼yuiop)独家发布

    从初TV开发到现在,在移动边框上用过很多方法。

    下面我来简单的列出来使用过那些解决方法和思路:

    • 1,在所有需要放大和设置边框的View下方嵌套一层FrameLayout,作为放大的背景的容器。焦点移动上去,算出当前View的大小,然后再设置FrameLayout的大小与.9图片并bringtoFront();
    • 2,为每个需要放大与突出的View设置shape和selector,这个是我最推荐的方法,现在很多TV的APP都采用这种,但是有个缺点,发光和阴影并不能设置。这与需要稍微有点炫酷效果的桌面有点不符合。
    • 3,全局FrameLayout,这个是我现在在用的方法,现在已经整理成一套框架,不久就会开源,现在还有示例Demo未完成。

    下面让我们来进入我的框架的主题来看一下:

    红圈所标出来的是几个主要的类与自定义View,下面我们来深入(我在设计的时候,焦点处理是各自处理各自的,解耦)。

    先上两幅比较难的界面(重点在于焦点的处理与动画的处理,图一有动态的添加和删除)。

    最主要的接口MoveAnimationHelper(做动画效果的)如下:

    /**
     * Created by ShanCanCan on 2016/4/3 0003.
     */
    
    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 主要实现边框的生成与移动,还有阴影的添加

    /**
     *
     * Created by ShanCanCan on 2016/4/3 0003.
     */
    
    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;
            setWillNotDraw(false);//必须要设置,如果我们想要重写onDraw,就要调用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。

    
    
        @Override
        public void drawMoveView(Canvas canvas) {
            canvas.save();
    
            if (!isDrawUpRect) {//飞框的绘制顺序,
    
                onDrawShadow(canvas);
    
                onDrawUpRect(canvas);
            }
            // 绘制焦点子控件.
            if (mFocusView != null && (!isDrawUpRect && isDrawing)) {
                onDrawFocusView(canvas);
            }
            //
            if (isDrawUpRect) {//飞框的绘制顺序
    
                onDrawShadow(canvas);
    
                onDrawUpRect(canvas);
            }
            canvas.restore();
    
        }
      @Override
        public void setFocusView(View currentView, View oldView, float scale) {
    
            mFocusView = currentView;
            int getScale = (int) (scale * 10);
            if (getScale > 10) {
                if (currentView != null) {
    
                    currentView.animate().scaleX(scale).scaleY(scale).setDuration(DEFAULT_TRAN_DUR_ANIM).start();
                    if (oldView != null) {
                        oldView.animate().scaleX(DEFUALT_SCALE).scaleY(DEFUALT_SCALE).setDuration(DEFAULT_TRAN_DUR_ANIM).start();
                    }
                }
            }
            rectMoveAnimation(currentView, scale, scale);
    
        }
    
        @Override
        public void rectMoveAnimation(View currentView, float scaleX, float scaleY) {
            Rect fromRect = findLocationWithView(getMoveView());
            Rect toRect = findLocationWithView(currentView);
            int disX = toRect.left - fromRect.left;
            int disY = toRect.top - fromRect.top;
            rectMoveMainLogic(currentView, disX, disY, scaleX, scaleY);
        }
    
        private Rect findLocationWithView(View view) {
            ViewGroup root = (ViewGroup) getMoveView().getParent();
            Rect rect = new Rect();
            root.offsetDescendantRectToMyCoords(view, rect);
            return rect;
        }
    
        private void rectMoveMainLogic(final View focusView, float x, float y, float scaleX, float scaleY) {
            int newWidth = 0;
            int newHeight = 0;
            int oldWidth = 0;
            int oldHeight = 0;
            if (focusView != null) {
                newWidth = (int) (focusView.getMeasuredWidth() * scaleX);
                newHeight = (int) (focusView.getMeasuredHeight() * scaleY);
                x = x + (focusView.getMeasuredWidth() - newWidth) / 2;
                y = y + (focusView.getMeasuredHeight() - newHeight) / 2;
            }
    
            // 取消之前的动画.
            if (mCombineAnimatorSet != null)
                mCombineAnimatorSet.cancel();
    
            oldWidth = getMoveView().getMeasuredWidth();
            oldHeight = getMoveView().getMeasuredHeight();
    
            ObjectAnimator transAnimatorX = ObjectAnimator.ofFloat(getMoveView(), "translationX", x);
            ObjectAnimator transAnimatorY = ObjectAnimator.ofFloat(getMoveView(), "translationY", y);
            ObjectAnimator scaleXAnimator = ObjectAnimator.ofInt(new ScaleTool(getMoveView()), "width", oldWidth,
                    (int) newWidth);
            ObjectAnimator scaleYAnimator = ObjectAnimator.ofInt(new ScaleTool(getMoveView()), "height", oldHeight,
                    (int) newHeight);
            //
            AnimatorSet mAnimatorSet = new AnimatorSet();
            mAnimatorSet.playTogether(transAnimatorX, transAnimatorY, scaleXAnimator, scaleYAnimator);
            mAnimatorSet.setInterpolator(new DecelerateInterpolator(1));
            mAnimatorSet.setDuration(DEFAULT_TRAN_DUR_ANIM);
            getMoveView().setVisibility(View.VISIBLE);
    
            mAnimatorSet.addListener(new Animator.AnimatorListener() {//动画的监听,主要用来设置移动飞框的绘制顺序
                @Override
                public void onAnimationStart(Animator animation) {
                    if (!isDrawUpRect)
                        isDrawing = false;
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
                    if (!isDrawUpRect)
                        isDrawing = false;
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (!isDrawUpRect)
                        isDrawing = true;
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
                    if (!isDrawUpRect)
                        isDrawing = false;}
            });
    
            mAnimatorSet.start();
            mCombineAnimatorSet = mAnimatorSet;
    
        }
    

    下面是绘制边框和绘制阴影的方法,这次方法中可以动态的调节移动边框的大小,实现全包裹或者是类似于padding的效果。

    
    
        /**
         * 绘制最上层的移动边框.
         */
        public void onDrawUpRect(Canvas canvas) {
            Drawable drawableUp = getMoveView().getUpRectDrawable();
            if (drawableUp != null) {
                RectF paddingRect = getMoveView().getDrawUpRect();//从MoveView()中获取的,你可以自己在activity调节。
                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) {
                RectF shadowPaddingRect = getMoveView().getDrawShadowRect();//从MoveView()中获取的,你可以自己在activity调节。
                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 ,用来包裹我们所有的控件,这样,主要是为了放大的时候,控件不会被挡住

    package com.shancancan.tvdemos.views;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.widget.RelativeLayout;
    
    /**
     *
     * Created by ShanCanCan on 2016/4/3 0003.
     */
    
    public class MainRelativeLayout extends RelativeLayout {
    
        private int position;
    
    
    
        public MainRelativeLayout(Context context) {
            super(context);
            init(context);
        }
    
        public MainRelativeLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public MainRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
    
    
    
            private void init(Context context){
                setClipChildren(false); //是否现限制其他控件在它周围绘制选择false
                setClipToPadding(false); //是否限制控件区域在padding里面
                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;
        }
    }
    

    使用方法两步走:

    一,布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <com.shancancan.tvdemos.views.MainRelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_entry"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:background="@drawable/default_wallpaper"
        tools:context="com.shancancan.tvdemos.activities.EntryActivity">
    <!--android:clipChildren="false"//是否现限制其他控件在它周围绘制选择false
        android:clipToPadding="false" //是否限制控件区域在padding里面
        根部局必须要加这两句话,其它父布局按需添加
        布局文件最下方介绍-->
    
        <com.shancancan.tvdemos.views.RoundImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:scaleType="fitXY"
            android:focusable="true"
            app:borderRadius="5dp"
            app:type="round"
            android:src="@drawable/beijing7"
            android:layout_alignParentBottom="true"
            android:layout_alignParentStart="true"
            android:layout_marginBottom="28dp"
            android:id="@+id/roundImageView3"/>
    
        <com.shancancan.tvdemos.views.RoundImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            app:borderRadius="5dp"
            app:type="round"
            android:focusable="true"
            android:scaleType="fitXY"
            android:src="@drawable/beijing5"
            android:layout_alignTop="@+id/roundImageView3"
            android:layout_alignStart="@+id/roundImageView4"
            android:id="@+id/roundImageView5"/>
    
        <com.shancancan.tvdemos.views.RoundImageView
            android:layout_width="400dp"
            android:layout_height="200dp"
            app:borderRadius="5dp"
            app:type="round"
            android:focusable="true"
            android:scaleType="fitXY"
            android:src="@drawable/beijing3"
            android:id="@+id/roundImageView7"
            android:layout_alignTop="@+id/roundImageView5"
            android:layout_toEndOf="@+id/roundImageView5"
            android:layout_marginStart="117dp"/>
    
    
        <com.shancancan.tvdemos.views.RoundImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            app:borderRadius="15dp"
            app:type="round"
            android:focusable="true"
            android:scaleType="fitXY"
            android:src="@drawable/beijing1"
            android:id="@+id/roundImageView2"/>
    
    
        <com.shancancan.tvdemos.views.RoundImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            app:borderRadius="5dp"
            app:type="round"
            android:focusable="true"
            android:scaleType="fitXY"
            android:src="@drawable/beijing4"
            android:id="@+id/roundImageView6"
            android:layout_alignParentTop="true"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="128dp"/>
    
        <com.shancancan.tvdemos.views.RoundImageView
            android:layout_width="300dp"
            android:layout_height="100dp"
            android:scaleType="fitXY"
            android:src="@drawable/beijing6"
            app:borderRadius="5dp"
            app:type="round"
            android:focusable="true"
            android:id="@+id/roundImageView4"
            android:layout_alignBottom="@+id/roundImageView2"
            android:layout_toStartOf="@+id/roundImageView6"
            android:layout_marginEnd="34dp"/>
    <!--MoveFrameLayout必须在根布局之上,而且不能被其他的控件位置上有引用-->
     <com.shancancan.tvdemos.views.MoveFrameLayout
            android:id="@+id/entrymove"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
    
        </com.shancancan.tvdemos.views.MoveFrameLayout>
    
    <!--根布局用MainRelativeLayout-->
    </com.shancancan.tvdemos.views.MainRelativeLayout>
    
    这里写图片描述

    二,activity处理

    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() {
            mMoveView.setUpRectResource(R.drawable.conner);//这里也可以设置shape或者是.9图片
            float density = getResources().getDisplayMetrics().density;//调整大小,如果你的边框大了就修改w_或者h_这两个参数
            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.setUpPaddingRect(receF);//重新为mMoveView设置大小
            mMoveView.setTranDurAnimTime(400);
        }
        public float getDimension(int id) {
            return getResources().getDimension(id);
        }
        private void initRelativeLayout() {//这是焦点的全局监听方法,与OnFocusChangeListener不同,这个方法长安不执行。
    
            mRelativeLayout.getViewTreeObserver().addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
                @Override
                public void onGlobalFocusChanged(View oldFocus, View newFocus) {
    
                    if (newFocus != null) {
                       // newFocus.bringToFront();
                        mMoveView.setDrawUpRectEnabled(true);//设置居于放大的view之上。
                        float scale = 1.1f;
                        mMoveView.setFocusView(newFocus, mOldFocus, scale);
                        mMoveView.bringToFront();//将mMoveView的位置bringToFront()
                        mOldFocus = newFocus;//自己将移动后的View进行保存,
    
                    }
                }
            });
        }
    }
    

    大功告成了,简单吧?你可以先下载体验一下,也可以关注我,后续提供更多示例,RecyclerView,带有指示器的ViewPager等等。

    Demo尚未完成,先传百度云,点击即可下载与CSDN下载,完成后我会将其上传至Jcenter和github,大家直接compile就行了。

    相关文章

      网友评论

          本文标题:两步集成TV移动框架,从未如此简单

          本文链接:https://www.haomeiwen.com/subject/sodkxxtx.html