转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52822499

前言:上一篇介绍了仿泰捷视频TV版的效果,对应github:https://github.com/hejunlin2013/TVSample,今天就介绍下对应的源码部分

先看下View的层级结构图:

在SmoothHorizonalScrollView(继承HorizonalScrollView)下挂上DrawingOrderRelativeLayout(继承自RelativeLayout),接下来就是一个个的MetroItem,每个Item下包含ImageView(海报图),CornerView(VIP角标),TextView(海报图简介),像第一张和第二张,以及最后两张,显示是频道入口,只有一个ImageView,无CornerView及TextView。

角标View

public class CornerVew extends View {

    private String mTextContent;//显示字体内容
    private int mTextColor;//字体颜色
    private float mTextSize;//字体大小
    private boolean mTextBold;//是否字体加粗
    private boolean mFillTriangle;//是否三角形
    private boolean mTextAllCaps;//
    private int mBackgroundColor;//背景颜色
    private float mMinSize;//最小值
    private float mPadding;//padding值
    private int mGravity;//
    private static final int DEFAULT_DEGREES = 45;//切角

    private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
    private Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Path mPath = new Path();

    public CornerVew(Context context) {
        this(context, null);
    }

    public CornerVew(Context context, AttributeSet attrs) {
        super(context, attrs);

        obtainAttributes(context, attrs);
        mTextPaint.setTextAlign(Paint.Align.CENTER);//从中间开始画
    }

    private void obtainAttributes(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CornerVew);
        mTextContent = ta.getString(R.styleable.CornerVew_cv_text);
        mTextColor = ta.getColor(R.styleable.CornerVew_cv_text_color, Color.parseColor("#ffffff"));
        mTextSize = ta.getDimension(R.styleable.CornerVew_cv_text_size, sp2px(11));
        mTextBold = ta.getBoolean(R.styleable.CornerVew_cv_text_bold, true);//加粗
        mTextAllCaps = ta.getBoolean(R.styleable.CornerVew_cv_text_all_caps, true);
        mFillTriangle = ta.getBoolean(R.styleable.CornerVew_cv_fill_triangle, false);
        mBackgroundColor = ta.getColor(R.styleable.CornerVew_cv_background_color, Color.parseColor("#FF4081"));
        mMinSize = ta.getDimension(R.styleable.CornerVew_cv_min_size, mFillTriangle ? dp2px(35) : dp2px(50));
        mPadding = ta.getDimension(R.styleable.CornerVew_cv_padding, dp2px(3.5f));
        mGravity = ta.getInt(R.styleable.CornerVew_cv_gravity, Gravity.TOP | Gravity.LEFT);
        ta.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int size = getHeight();

        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setFakeBoldText(mTextBold);
        mBackgroundPaint.setColor(mBackgroundColor);

        float textHeight = mTextPaint.descent() - mTextPaint.ascent();
        if (mFillTriangle) {
            if (mGravity == (Gravity.TOP | Gravity.LEFT)) {//左上角
                mPath.reset();
                mPath.moveTo(0, 0);
                mPath.lineTo(0, size);
                mPath.lineTo(size, 0);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawTextWhenFill(size, -DEFAULT_DEGREES, canvas, true);
            } else if (mGravity == (Gravity.TOP | Gravity.RIGHT)) {//右上角
                mPath.reset();
                mPath.moveTo(size, 0);
                mPath.lineTo(0, 0);
                mPath.lineTo(size, size);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawTextWhenFill(size, DEFAULT_DEGREES, canvas, true);
            } else if (mGravity == (Gravity.BOTTOM | Gravity.LEFT)) {//左下角
                mPath.reset();
                mPath.moveTo(0, size);
                mPath.lineTo(0, 0);
                mPath.lineTo(size, size);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawTextWhenFill(size, DEFAULT_DEGREES, canvas, false);
            } else if (mGravity == (Gravity.BOTTOM | Gravity.RIGHT)) {//右下角
                mPath.reset();
                mPath.moveTo(size, size);
                mPath.lineTo(0, size);
                mPath.lineTo(size, 0);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawTextWhenFill(size, -DEFAULT_DEGREES, canvas, false);
            }
        } else {
            double delta = (textHeight + mPadding * 2) * Math.sqrt(2);
            if (mGravity == (Gravity.TOP | Gravity.LEFT)) {
                mPath.reset();
                mPath.moveTo(0, (float) (size - delta));
                mPath.lineTo(0, size);
                mPath.lineTo(size, 0);
                mPath.lineTo((float) (size - delta), 0);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawText(size, -DEFAULT_DEGREES, canvas, textHeight, true);
            } else if (mGravity == (Gravity.TOP | Gravity.RIGHT)) {
                mPath.reset();
                mPath.moveTo(0, 0);
                mPath.lineTo((float) delta, 0);
                mPath.lineTo(size, (float) (size - delta));
                mPath.lineTo(size, size);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawText(size, DEFAULT_DEGREES, canvas, textHeight, true);
            } else if (mGravity == (Gravity.BOTTOM | Gravity.LEFT)) {
                mPath.reset();
                mPath.moveTo(0, 0);
                mPath.lineTo(0, (float) delta);
                mPath.lineTo((float) (size - delta), size);
                mPath.lineTo(size, size);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawText(size, DEFAULT_DEGREES, canvas, textHeight, false);
            } else if (mGravity == (Gravity.BOTTOM | Gravity.RIGHT)) {
                mPath.reset();
                mPath.moveTo(0, size);
                mPath.lineTo((float) delta, size);
                mPath.lineTo(size, (float) delta);
                mPath.lineTo(size, 0);
                mPath.close();
                canvas.drawPath(mPath, mBackgroundPaint);

                drawText(size, -DEFAULT_DEGREES, canvas, textHeight, false);
            }
        }
    }

    private void drawText(int size, float degrees, Canvas canvas, float textHeight, boolean isTop) {
        canvas.save();
        canvas.rotate(degrees, size / 2f, size / 2f);
        float delta = isTop ? -(textHeight + mPadding * 2) / 2 : (textHeight + mPadding * 2) / 2;
        float textBaseY = size / 2 - (mTextPaint.descent() + mTextPaint.ascent()) / 2 + delta;
        canvas.drawText(mTextAllCaps ? mTextContent.toUpperCase() : mTextContent,
                getPaddingLeft() + (size - getPaddingLeft() - getPaddingRight()) / 2, textBaseY, mTextPaint);
        canvas.restore();
    }

    private void drawTextWhenFill(int size, float degrees, Canvas canvas, boolean isTop) {
        canvas.save();
        canvas.rotate(degrees, size / 2f, size / 2f);
        float delta = isTop ? -size / 4 : size / 4;
        float textBaseY = size / 2 - (mTextPaint.descent() + mTextPaint.ascent()) / 2 + delta;
        canvas.drawText(mTextAllCaps ? mTextContent.toUpperCase() : mTextContent,
                getPaddingLeft() + (size - getPaddingLeft() - getPaddingRight()) / 2, textBaseY, mTextPaint);
        canvas.restore();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measuredWidth = measureWidth(widthMeasureSpec);
        setMeasuredDimension(measuredWidth, measuredWidth);
    }

    private int measureWidth(int widthMeasureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);//获取测量模式
        int specSize = MeasureSpec.getSize(widthMeasureSpec);//获取测量大小
        if (specMode == MeasureSpec.EXACTLY) {//精确模式
            result = specSize;
        } else {
            int padding = getPaddingLeft() + getPaddingRight();
            mTextPaint.setColor(mTextColor);
            mTextPaint.setTextSize(mTextSize);
            float textWidth = mTextPaint.measureText(mTextContent + "");
            result = (int) ((padding + (int) textWidth) * Math.sqrt(2));
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
            result = Math.max((int) mMinSize, result);
        }

        return result;
    }

    protected int dp2px(float dp) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

    protected int sp2px(float sp) {
        final float scale = getResources().getDisplayMetrics().scaledDensity;
        return (int) (sp * scale + 0.5f);
    }
}

绘制子视图顺序的Layout

public class DrawingOrderRelativeLayout extends RelativeLayout {
    private int position = 0;

    public DrawingOrderRelativeLayout(Context context) {
        super(context);
    }

    public DrawingOrderRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setChildrenDrawingOrderEnabled(true);
    }

    public void setCurrentPosition(int pos) {
        this.position = pos;
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
            View focused = findFocus();//找到焦点
            int pos = indexOfChild(focused);
            if (pos >= 0 && pos < getChildCount()) {
                setCurrentPosition(pos);
                postInvalidate();
            }

        return super.dispatchKeyEvent(event);
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {//改变ViewGroup的子视图绘制顺序
        View v = getFocusedChild();
        int pos = indexOfChild(v);
        if (pos >= 0 && pos < childCount)
            setCurrentPosition(pos);
        if (i == childCount - 1) {//从最后一个view开始绘制
            return position;
        }
        if (i == position) {
            return childCount - 1;
        }
        return i;
    }

}

MetroItem


public class MetroItemFrameLayout extends FrameLayout implements IMetroItemRound {

    private MetroItemRound mMetroItemRound;
    public MetroItemFrameLayout(Context context) {
        super(context);
        init(context, null, 0);
    }

    public MetroItemFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public MetroItemFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs, defStyle);
    }

    private void init(Context context, AttributeSet attrs, int defStyle) {
        mMetroItemRound = new MetroItemRound(this, context, attrs, defStyle);
        setWillNotDraw(false);//设置为false时,就会调用自定义的布局
    }

    @Override
    public void draw(Canvas canvas) {
        mMetroItemRound.draw(canvas);
    }

    @Override
    public MetroItemRound getRoundImpl() {
        return mMetroItemRound;
    }

    @Override
    public void drawRadious(Canvas canvas) {
        super.draw(canvas);
    }
}

MetroItem的圆角

public class MetroItemRound {

    private float mRadius;//圆角
    private float mTopLeftRadius;//左上角的圆角
    private float mTopRightRadius;//右上角的圆角
    private float mBottomLeftRadius;//左下角的圆角
    private float mBottomRightRadius;//右下角的圆角
    private Paint mPaint;//画笔
    private Path mPath;//画布
    private IMetroItemRound mView;

    public MetroItemRound(IMetroItemRound view, Context context, AttributeSet attrs, int defStyle) {
        init(context, attrs, defStyle);
        mView = view;
    }

    private void init(Context context, AttributeSet attrs, int defStyle) {
        mPaint = new Paint();//实例化画笔
        mPaint.setColor(Color.WHITE);//设置画笔颜色为白色
        mPaint.setAntiAlias(true);//设置抗锯齿
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));//取下层绘制非交集部分
        mPath = new Path();//设置路径

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MetroItemRound, defStyle, 0);
        mBottomLeftRadius = a.getDimension(R.styleable.MetroItemRound_bottomLeftRadius, -1);
        mBottomRightRadius = a.getDimension(R.styleable.MetroItemRound_bottomRightRadius, -1);
        mTopLeftRadius = a.getDimension(R.styleable.MetroItemRound_topLeftRadius, -1);
        mTopRightRadius = a.getDimension(R.styleable.MetroItemRound_topRightRadius, -1);
        mRadius = a.getDimension(R.styleable.MetroItemRound_radius, -1);

        if (mRadius > 0) {
            if (mBottomLeftRadius < 0)
                mBottomLeftRadius = mRadius;
            if (mBottomRightRadius < 0)
                mBottomRightRadius = mRadius;
            if (mTopLeftRadius < 0)
                mTopLeftRadius = mRadius;
            if (mTopRightRadius < 0)
                mTopRightRadius = mRadius;
        }
        a.recycle();

    }

    public void draw(Canvas canvas) {
        if (mBottomLeftRadius <= 0 && mBottomRightRadius <= 0 && mTopRightRadius <= 0 && mTopLeftRadius <= 0) {
            mView.drawRadious(canvas);
            return;
        }

        int width = mView.getWidth();
        int height = mView.getHeight();

        int count = canvas.save();
        int count2 = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);

        addRoundPath(width, height);
        mView.drawRadious(canvas);//画一个圆角
        canvas.drawPath(mPath, mPaint);
        canvas.restoreToCount(count2);
        canvas.restoreToCount(count);

    }

    private void addRoundPath(int width, int height) {
        //topleft path
        if (mTopLeftRadius > 0) {
            Path topLeftPath = new Path();
            topLeftPath.moveTo(0, mTopLeftRadius);//移动画笔到最左边x为0,y为mTopLeftRadius
            topLeftPath.lineTo(0, 0);//绘制直线,从坐标0,0开始
            topLeftPath.lineTo(mTopLeftRadius, 0);//
            RectF arc = new RectF(0, 0, mTopLeftRadius * 2, mTopLeftRadius * 2);//矩形区域
            //和Rect的区别是RectF表示精确到浮点型
            topLeftPath.arcTo(arc, -90, -90);//画弧线的路径,从-90到-90,逆时针画
            topLeftPath.close();//
            mPath.addPath(topLeftPath);
        }

        //topRight path
        if (mTopRightRadius > 0) {
            Path topRightPath = new Path();
            topRightPath.moveTo(width, mTopRightRadius);//移动画笔到最右边x为width,y为mTopRightRadius
            topRightPath.lineTo(width, 0);
            topRightPath.lineTo(width - mTopRightRadius, 0);
            RectF arc = new RectF(width - mTopRightRadius * 2, 0, width, mTopRightRadius * 2);

            topRightPath.arcTo(arc, -90, 90);//这里是画180的椭圆
            topRightPath.close();

            mPath.addPath(topRightPath);

        }

        //bottomLeft path
        if (mBottomLeftRadius > 0) {
            Path bottomLeftPath = new Path();
            bottomLeftPath.moveTo(0, height - mBottomLeftRadius);
            bottomLeftPath.lineTo(0, height);
            bottomLeftPath.lineTo(mBottomLeftRadius, height);
            RectF arc = new RectF(0, height - mBottomLeftRadius * 2, mBottomLeftRadius * 2, height);

            bottomLeftPath.arcTo(arc, 90, 90);
            bottomLeftPath.close();
            mPath.addPath(bottomLeftPath);
        }

        //bottomRight path
        if (mBottomRightRadius > 0) {
            Path bottomRightPath = new Path();
            bottomRightPath.moveTo(width - mBottomRightRadius, height);
            bottomRightPath.lineTo(width, height);
            bottomRightPath.lineTo(width, height - mBottomRightRadius);
            RectF arc = new RectF(width - mBottomRightRadius * 2, height - mBottomRightRadius * 2, width, height);
            bottomRightPath.arcTo(arc, 0, 90);
            bottomRightPath.close();
            mPath.addPath(bottomRightPath);
        }

    }
}

MetroItem中外边框处理类

public class MetroViewBorderHandler implements IMetroViewBorder {

    private String TAG = MetroViewBorderHandler.class.getSimpleName();
    protected boolean mScalable = true;//是否缩小
    protected float mScale = 1.1f;//设置放大比例

    protected long mDurationTraslate = 200;//焦点移动的动画时间
    protected int mMargin = 0;
    protected View lastFocus, oldLastFocus;//上一个焦点,新的位置的焦点
    protected AnimatorSet mAnimatorSet;//动画集合,可组合多个动画
    protected List<Animator> mAnimatorList = new ArrayList<Animator>();
    protected View mTarget;

    protected boolean mEnableTouch = true;

    public MetroViewBorderHandler() {
        mFocusListener.add(mFocusMoveListener);//设置焦点移动时监听
        mFocusListener.add(mFocusScaleListener);//设置焦点放大缩小监听
        mFocusListener.add(mFocusPlayListener);
        mFocusListener.add(mAbsListViewFocusListener);//用作listview,gridview相关
    }

    public interface FocusListener {
        void onFocusChanged(View oldFocus, View newFocus);
    }

    protected List<FocusListener> mFocusListener = new ArrayList<FocusListener>(1);
    protected List<Animator.AnimatorListener> mAnimatorListener = new ArrayList<Animator.AnimatorListener>(1);

    public FocusListener mFocusScaleListener = new FocusListener() {
        @Override
        public void onFocusChanged(View oldFocus, View newFocus) {
            //焦点变化,设置新焦点移动的位置变成放大状态
            mAnimatorList.addAll(getScaleAnimator(newFocus, true));
            if (oldFocus != null) {
                //上一个view(之前是焦点态,onFocusChanged变成非焦点态)不为null,设置上一个为缩小状态
                mAnimatorList.addAll(getScaleAnimator(oldFocus, false));
            }
        }
    };

    public FocusListener mFocusPlayListener = new FocusListener() {
        @Override
        public void onFocusChanged(View oldFocus, View newFocus) {
            try {
                if (newFocus instanceof AbsListView) {//如果新的view是AbsListView的实例,直接return
                    return;
                }
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.setInterpolator(new DecelerateInterpolator(1));//设置插值器
                animatorSet.setDuration(mDurationTraslate);//设置动画时间
                animatorSet.playTogether(mAnimatorList);//表示两个动画同进执行
                for (Animator.AnimatorListener listener : mAnimatorListener) {
                    animatorSet.addListener(listener);
                }
                mAnimatorSet = animatorSet;
                if (oldFocus == null) {//之前view为null,表示首次状态时
                    animatorSet.setDuration(0);//无动画时长
                    mTarget.setVisibility(View.VISIBLE);
                }
                animatorSet.start();//开启动画
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    };

    public FocusListener mFocusMoveListener = new FocusListener() {
        @Override
        public void onFocusChanged(View oldFocus, View newFocus) {
            if (newFocus == null) return;//下一个view不存在时,直接return
            try {
                mAnimatorList.addAll(getMoveAnimator(newFocus, 0, 0));//添加
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    };

    public FocusListener mAbsListViewFocusListener = new FocusListener() {

        @Override
        public void onFocusChanged(View oldFocus, View newFocus) {
            try {
                if (oldFocus == null) {
                    for (int i = 0; i < attacheViews.size(); i++) {
                        View view = attacheViews.get(i);
                        if (view instanceof AbsListView) {
                            final AbsListView absListView = (AbsListView) view;
                            mTarget.setVisibility(View.INVISIBLE);
                            if (mFirstFocus) {
                                absListView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                                    @Override
                                    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                                        try {
                                            absListView.removeOnLayoutChangeListener(this);
                                            int factorX = 0, factorY = 0;
                                            Rect rect = new Rect();
                                            View firstView = (View) absListView.getSelectedView();
                                            firstView.getLocalVisibleRect(rect);
                                            if (Math.abs(rect.left - rect.right) > firstView.getMeasuredWidth()) {
                                                factorX = (Math.abs(rect.left - rect.right) - firstView.getMeasuredWidth()) / 2 - 1;
                                                factorY = (Math.abs(rect.top - rect.bottom) - firstView.getMeasuredHeight()) / 2;
                                            }
                                            List<Animator> animatorList = new ArrayList<Animator>(3);
                                            animatorList.addAll(getScaleAnimator(firstView, true));
                                            animatorList.addAll(getMoveAnimator(firstView, factorX, factorY));
                                            mTarget.setVisibility(View.VISIBLE);
                                            AnimatorSet animatorSet = new AnimatorSet();
                                            animatorSet.setDuration(0);
                                            animatorSet.playTogether(animatorList);
                                            animatorSet.start();
                                        } catch (Exception ex) {
                                            ex.printStackTrace();
                                        }
                                    }
                                });

                            }
                            break;
                        }
                    }
                } else if (oldFocus instanceof AbsListView && newFocus instanceof AbsListView) {
                    if (attacheViews.indexOf(oldFocus) >= 0 && attacheViews.indexOf(newFocus) >= 0) {

                        AbsListView a = (AbsListView) oldFocus;
                        AbsListView b = (AbsListView) newFocus;

                        MyOnItemSelectedListener oldOn = (MyOnItemSelectedListener) onItemSelectedListenerList.get(oldFocus);
                        MyOnItemSelectedListener newOn = (MyOnItemSelectedListener) onItemSelectedListenerList.get(newFocus);

                        int factorX = 0, factorY = 0;
                        Rect rect = new Rect();
                        View firstView = (View) b.getSelectedView();
                        firstView.getLocalVisibleRect(rect);
                        if (Math.abs(rect.left - rect.right) > firstView.getMeasuredWidth()) {
                            factorX = (Math.abs(rect.left - rect.right) - firstView.getMeasuredWidth()) / 2 - 1;
                            factorY = (Math.abs(rect.top - rect.bottom) - firstView.getMeasuredHeight()) / 2;
                        }

                        List<Animator> animatorList = new ArrayList<Animator>(3);
                        animatorList.addAll(getScaleAnimator(firstView, true));
                        animatorList.addAll(getScaleAnimator(a.getSelectedView(), false));

                        animatorList.addAll(getMoveAnimator(firstView, factorX, factorY));
                        mTarget.setVisibility(View.VISIBLE);

                        mAnimatorSet = new AnimatorSet();

                        mAnimatorSet.setDuration(mDurationTraslate);
                        mAnimatorSet.playTogether(animatorList);
                        mAnimatorSet.start();

                        oldOn.oldFocus = null;
                        oldOn.newFocus = null;

                        newOn.oldFocus = null;
                        if (newOn.newFocus != null && newOn.oldFocus != null) {
                            newOn.newFocus = null;
                        } else {
                            newOn.newFocus = b.getSelectedView();
                        }

                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    };

    protected List<Animator> getScaleAnimator(View view, boolean isScale) {

        List<Animator> animatorList = new ArrayList<Animator>(2);
        if (!mScalable) return animatorList;//如果没有放大,直接返回
        try {
            float scaleBefore = 1.0f;//放大前比例
            float scaleAfter = mScale;//放大后比例
            if (!isScale) {//
                scaleBefore = mScale;
                scaleAfter = 1.0f;
            }
            ObjectAnimator scaleX = new ObjectAnimator().ofFloat(view, "scaleX", scaleBefore, scaleAfter);
            ObjectAnimator scaleY = new ObjectAnimator().ofFloat(view, "scaleY", scaleBefore, scaleAfter);
            animatorList.add(scaleX);
            animatorList.add(scaleY);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return animatorList;
    }

    protected List<Animator> getMoveAnimator(View newFocus, int factorX, int factorY) {

        List<Animator> animatorList = new ArrayList<Animator>();
        int newXY[];
        int oldXY[];

        try {

            newXY = getLocation(newFocus);//新的
            oldXY = getLocation(mTarget);

            int newWidth;
            int newHeight;
            int oldWidth = mTarget.getMeasuredWidth();
            int oldHeight = mTarget.getMeasuredHeight();

            if (mScalable) {
                float scaleWidth = newFocus.getMeasuredWidth() * mScale;
                float scaleHeight = newFocus.getMeasuredHeight() * mScale;
                newWidth = (int) (scaleWidth + mMargin * 2 + 0.5);
                newHeight = (int) (scaleHeight + mMargin * 2 + 0.5);
                newXY[0] = (int) (newXY[0] - (newWidth - newFocus.getMeasuredWidth()) / 2.0f) + factorX;
                newXY[1] = (int) (newXY[1] - (newHeight - newFocus.getMeasuredHeight()) / 2.0f + 0.5 + factorY);

            } else {
                newWidth = newFocus.getWidth();
                newHeight = newFocus.getHeight();
            }
            if (oldHeight == 0 && oldWidth == 0) {
                oldHeight = newHeight;
                oldWidth = newWidth;
            }

            PropertyValuesHolder valuesWithdHolder = PropertyValuesHolder.ofInt("width", oldWidth, newWidth);
            PropertyValuesHolder valuesHeightHolder = PropertyValuesHolder.ofInt("height", oldHeight, newHeight);
            PropertyValuesHolder valuesXHolder = PropertyValuesHolder.ofFloat("translationX", oldXY[0], newXY[0]);
            PropertyValuesHolder valuesYHolder = PropertyValuesHolder.ofFloat("translationY", oldXY[1], newXY[1]);
            final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mTarget, valuesWithdHolder, valuesHeightHolder, valuesYHolder, valuesXHolder);

            scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public synchronized void onAnimationUpdate(ValueAnimator animation) {
                    int width = (int) animation.getAnimatedValue("width");
                    int height = (int) animation.getAnimatedValue("height");
                    float translationX = (float) animation.getAnimatedValue("translationX");
                    float translationY = (float) animation.getAnimatedValue("translationY");
                    View view = (View) scaleAnimator.getTarget();
                    assert view != null;
                    int w = view.getLayoutParams().width;
                    view.getLayoutParams().width = width;
                    view.getLayoutParams().height = height;
                    if (width > 0) {
                        view.requestLayout();
                        view.postInvalidate();

                    }
                }
            });
            animatorList.add(scaleAnimator);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return animatorList;
    }

    protected int[] getLocation(View view) {
        int[] location = new int[2];//location[0]代表x坐标,location [1] 代表y坐标。
        try {
            //获取在整个屏幕内的绝对坐标,注意这个值是要从屏幕顶端算起,也就是包括了通知栏的高度。
            view.getLocationOnScreen(location);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return location;
    }

    public void addOnFocusChanged(FocusListener focusListener) {
        this.mFocusListener.add(focusListener);
    }

    public void removeOnFocusChanged(FocusListener focusListener) {
        this.mFocusListener.remove(focusListener);
    }

    public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
        this.mAnimatorListener.add(animatorListener);
    }

    public void removeAnimatorListener(Animator.AnimatorListener animatorListener) {
        this.mAnimatorListener.remove(animatorListener);
    }

    private class VisibleScope {
        public boolean isVisible;
        public View oldFocus;
        public View newFocus;
    }

    protected VisibleScope checkVisibleScope(View oldFocus, View newFocus) {
        VisibleScope scope = new VisibleScope();
        try {
            scope.oldFocus = oldFocus;
            scope.newFocus = newFocus;
            scope.isVisible = true;
            if (attacheViews.indexOf(oldFocus) >= 0 && attacheViews.indexOf(newFocus) >= 0) {
                return scope;
            }

            if (oldFocus != null && newFocus != null) {
                if (oldFocus.getParent() != newFocus.getParent()) {
                    if ((attacheViews.indexOf(newFocus.getParent()) < 0) || (attacheViews.indexOf(oldFocus.getParent()) < 0 && attacheViews.indexOf(newFocus.getParent()) > 0)) {
                        mTarget.setVisibility(View.INVISIBLE);
                        AnimatorSet animatorSet = new AnimatorSet();
                        animatorSet.playTogether(getScaleAnimator(oldFocus, false));
                        animatorSet.setDuration(0).start();
                        scope.isVisible = false;
                        return scope;
                    } else {
                        mTarget.setVisibility(View.VISIBLE);
                    }
                    if (attacheViews.indexOf(oldFocus.getParent()) < 0) {
                        scope.oldFocus = null;
                    }

                } else {
                    if (attacheViews.indexOf(newFocus.getParent()) < 0) {
                        mTarget.setVisibility(View.INVISIBLE);
                        scope.isVisible = false;
                        return scope;
                    }
                }
            }
            mTarget.setVisibility(View.VISIBLE);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return scope;
    }

    @Override
    public void onFocusChanged(View target, View oldFocus, View newFocus) {
        try {
            Log.d(TAG, "onFocusChanged:" + oldFocus + "=" + newFocus);

            if (newFocus == null && attacheViews.indexOf(newFocus) >= 0) {
                return;
            }
            if (oldFocus == newFocus)
                return;

            if (mAnimatorSet != null && mAnimatorSet.isRunning()) {//如果动画正在运行时
                mAnimatorSet.end();
            }

            lastFocus = newFocus;
            oldLastFocus = oldFocus;
            mTarget = target;

            VisibleScope scope = checkVisibleScope(oldFocus, newFocus);
            if (!scope.isVisible) {
                return;
            } else {
                oldFocus = scope.oldFocus;
                newFocus = scope.newFocus;
                oldLastFocus = scope.oldFocus;
            }

            if (isScrolling || newFocus == null || newFocus.getWidth() <= 0 || newFocus.getHeight() <= 0)
                return;

            mAnimatorList.clear();//清除动画

            for (FocusListener f : this.mFocusListener) {
                f.onFocusChanged(oldFocus, newFocus);
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void onScrollChanged(View target, View attachView) {
        try {
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void onLayout(View target, View attachView) {
        try {
            ViewGroup viewGroup = (ViewGroup) attachView.getRootView();
            if (target.getParent() != null && target.getParent() != viewGroup) {
                target.setVisibility(View.GONE);
                if (mFirstFocus)
                    viewGroup.requestFocus();//如果是首次获取焦点,强制变成焦点态
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    protected boolean mFirstFocus = true;

    public void setFirstFocus(boolean b) {
        this.mFirstFocus = b;
    }

    @Override
    public void onTouchModeChanged(View target, View attachView, boolean isInTouchMode) {
        try {
            if (mEnableTouch && isInTouchMode) {
                target.setVisibility(View.INVISIBLE);
                if (lastFocus != null) {
                    AnimatorSet animatorSet = new AnimatorSet();
                    animatorSet.playTogether(getScaleAnimator(lastFocus, false));
                    animatorSet.setDuration(0).start();
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    protected boolean isScrolling = false;

    protected List<View> attacheViews = new ArrayList<>();
    protected Map<View, AdapterView.OnItemSelectedListener> onItemSelectedListenerList = new HashMap<>();

    @Override
    public void onAttach(View target, View attachView) {
        try {
            mTarget = target;

            if (target.getParent() != null && (target.getParent() instanceof ViewGroup)) {
                ViewGroup vg = (ViewGroup) target.getParent();
                vg.removeView(target);
            }

            ViewGroup vg = (ViewGroup) attachView.getRootView();
            vg.addView(target);

            target.setVisibility(View.GONE);
            if (attachView instanceof RecyclerView) {
                RecyclerView recyclerView = (RecyclerView) attachView;
                RecyclerView.OnScrollListener recyclerViewOnScrollListener = null;
                recyclerViewOnScrollListener = new RecyclerView.OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                        try {
                            super.onScrollStateChanged(recyclerView, newState);

                            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                                isScrolling = false;
                                View oldFocus = oldLastFocus;
                                View newFocus = lastFocus;
                                VisibleScope scope = checkVisibleScope(oldFocus, newFocus);
                                if (!scope.isVisible) {
                                    return;
                                } else {
                                    oldFocus = scope.oldFocus;
                                    newFocus = scope.newFocus;
                                }
                                AnimatorSet animatorSet = new AnimatorSet();
                                List<Animator> list = new ArrayList<>();
                                list.addAll(getScaleAnimator(newFocus, true));
                                list.addAll(getMoveAnimator(newFocus, 0, 0));
                                animatorSet.setDuration(mDurationTraslate);
                                animatorSet.playTogether(list);
                                animatorSet.start();

                            } else if (newState == RecyclerView.SCROLL_STATE_SETTLING) {
                                isScrolling = true;
                                if (lastFocus != null) {
                                    List<Animator> list = getScaleAnimator(lastFocus, false);
                                    AnimatorSet animatorSet = new AnimatorSet();
                                    animatorSet.setDuration(150);
                                    animatorSet.playTogether(list);
                                    animatorSet.start();
                                }
                            }
                        } catch (Exception ex) {

                        }
                    }
                };
                recyclerView.addOnScrollListener(recyclerViewOnScrollListener);
            } else if (attachView instanceof AbsListView) {

                final AbsListView absListView = (AbsListView) attachView;
                final AdapterView.OnItemSelectedListener onItemSelectedListener = absListView.getOnItemSelectedListener();

                View temp = null;
                if (absListView.getChildCount() > 0) {
                    temp = absListView.getChildAt(0);
                }
                final View tempFocus = temp;
                MyOnItemSelectedListener myOnItemSelectedListener = new MyOnItemSelectedListener();
                myOnItemSelectedListener.onItemSelectedListener = onItemSelectedListener;
                myOnItemSelectedListener.oldFocus = temp;
                absListView.setOnItemSelectedListener(myOnItemSelectedListener);
                onItemSelectedListenerList.put(attachView, myOnItemSelectedListener);

            }

            attacheViews.add(attachView);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    protected class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
        public View oldFocus = null;
        public View newFocus = null;
        public AnimatorSet animatorSet;
        public AdapterView.OnItemSelectedListener onItemSelectedListener;

        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            try {
                if (onItemSelectedListener != null && parent != null) {
                    onItemSelectedListener.onItemSelected(parent, view, position, id);
                }
                if (newFocus == null)
                    return;
                newFocus = view;

                Rect rect = new Rect();
                view.getLocalVisibleRect(rect);
                ViewGroup vg = (ViewGroup) newFocus.getParent();

                int factorX = 0, factorY = 0;
                if (Math.abs(rect.left - rect.right) > newFocus.getMeasuredWidth()) {
                    factorX = (Math.abs(rect.left - rect.right) - newFocus.getMeasuredWidth()) / 2 - 1;
                    factorY = (Math.abs(rect.top - rect.bottom) - newFocus.getMeasuredHeight()) / 2;

                }

                List<Animator> animatorList = new ArrayList<Animator>(3);
                animatorList.addAll(getScaleAnimator(newFocus, true));
                if (oldFocus != null)
                    animatorList.addAll(getScaleAnimator(oldFocus, false));
                animatorList.addAll(getMoveAnimator(newFocus, factorX, factorY));
                mTarget.setVisibility(View.VISIBLE);

                if (animatorSet != null && animatorSet.isRunning())
                    animatorSet.end();
                animatorSet = new AnimatorSet();
                animatorSet.setDuration(mDurationTraslate);
                animatorSet.playTogether(animatorList);
                animatorSet.start();

                oldFocus = newFocus;
            } catch (Exception ex) {
                ex.printStackTrace();
            }

        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            if (onItemSelectedListener != null) {
                onItemSelectedListener.onNothingSelected(parent);
            }
        }
    }

    @Override
    public void OnDetach(View targe, View view) {
        if (targe.getParent() == view) {
            ((ViewGroup) view).removeView(targe);
        }

        attacheViews.remove(view);
    }

    public void setEnableTouch(boolean enableTouch) {
        this.mEnableTouch = enableTouch;
    }

    public boolean isScalable() {
        return mScalable;
    }

    public void setScalable(boolean scalable) {
        this.mScalable = scalable;
    }

    public float getScale() {
        return mScale;
    }

    public void setScale(float scale) {
        this.mScale = scale;
    }

    public int getMargin() {
        return mMargin;
    }

    public void setMargin(int mMargin) {
        this.mMargin = mMargin;
    }

}

MetroViewBorder用于对焦点变化处理及滚动处理

public class MetroViewBorderImpl<X extends View> implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnGlobalLayoutListener, ViewTreeObserver.OnTouchModeChangeListener {

    private static final String TAG = MetroViewBorderImpl.class.getSimpleName();

    private ViewGroup mViewGroup;
    private IMetroViewBorder mMetroViewBorder;

    private X mView;
    private View mLastView;

    public MetroViewBorderImpl(Context context) {
        this(context, null, 0);
    }

    public MetroViewBorderImpl(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MetroViewBorderImpl(Context context, AttributeSet attrs, int defStyleAttr) {
        init(context, attrs, defStyleAttr);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        mMetroViewBorder = new MetroViewBorderHandler();
        mView = (X) new View(context, attrs, defStyleAttr);
    }

    public MetroViewBorderImpl(X view) {
        this.mView = view;
        mMetroViewBorder = new MetroViewBorderHandler();
    }

    public MetroViewBorderImpl(X view, IMetroViewBorder border) {
        this.mView = view;
        mMetroViewBorder = border;
    }

    public MetroViewBorderImpl(Context context, int resId) {
        this((X) LayoutInflater.from(context).inflate(resId, null, false));
    }

    public X getView() {
        return mView;
    }

    public void setBackgroundResource(int resId) {
        if (mView != null)
            mView.setBackgroundResource(resId);
    }

    @Override
    public void onScrollChanged() {
        mMetroViewBorder.onScrollChanged(mView, mViewGroup);
    }

    @Override
    public void onGlobalLayout() {
        mMetroViewBorder.onLayout(mView, mViewGroup);
    }

    @Override
    public void onTouchModeChanged(boolean isInTouchMode) {
        mMetroViewBorder.onTouchModeChanged(mView, mViewGroup, isInTouchMode);
    }

    @Override
    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
        try {
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {// 4.3
                if (oldFocus == null && mLastView != null) {
                    oldFocus = mLastView;
                }
            }
            if (mMetroViewBorder != null)
                mMetroViewBorder.onFocusChanged(mView, oldFocus, newFocus);
            mLastView = newFocus;

        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    public <T extends MetroViewBorderHandler> T getViewBorder() {
        return (T) mMetroViewBorder;
    }

    public void setBorder(IMetroViewBorder border) {
        this.mMetroViewBorder = border;
    }

    public void attachTo(ViewGroup viewGroup) {
        try {
            if (viewGroup == null) {
                if (mView.getContext() instanceof Activity) {
                    Activity activity = (Activity) mView.getContext();
                    viewGroup = (ViewGroup) activity.getWindow().getDecorView().getRootView();//获取顶层view
                }
            }

            if (mViewGroup != viewGroup) {

                ViewTreeObserver viewTreeObserver = viewGroup.getViewTreeObserver();
                if (viewTreeObserver.isAlive() && mViewGroup == null) {
                    viewTreeObserver.addOnGlobalFocusChangeListener(this);
                    viewTreeObserver.addOnScrollChangedListener(this);
                    viewTreeObserver.addOnGlobalLayoutListener(this);
                    viewTreeObserver.addOnTouchModeChangeListener(this);
                }
                mViewGroup = viewGroup;
            }
            mMetroViewBorder.onAttach(mView, viewGroup);

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void detach() {
        detachFrom(mViewGroup);
    }

    public void detachFrom(ViewGroup viewGroup) {
        try {
            if (viewGroup == mViewGroup) {
                ViewTreeObserver viewTreeObserver = mViewGroup.getViewTreeObserver();//获取view树的观察者
                viewTreeObserver.removeOnGlobalFocusChangeListener(this);//通知全局性移除相应的listener
                viewTreeObserver.removeOnScrollChangedListener(this);
                viewTreeObserver.removeOnGlobalLayoutListener(this);
                viewTreeObserver.removeOnTouchModeChangeListener(this);
                mMetroViewBorder.OnDetach(mView, viewGroup);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。





如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

TV Metro界面(仿泰捷视频TV版)源码解析的更多相关文章

  1. Android TV开发总结(二)构建一个TV Metro界面(仿泰捷视频TV版)

    前言:上篇是介绍构建TV app前要知道的一些事儿,开发Android TV和手机本质上没有太大的区别,屏大,焦点处理,按键处理,是有别于有手机和Pad的实质区别.今天来介绍TV中Metro UI风格 ...

  2. Android TV开发总结(四)通过RecycleView构建一个TV app列表页(仿腾讯视频TV版)

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52854131 前言:昨晚看锤子手 ...

  3. 完美高仿精仿京东商城手机客户端android版源码

    完美高仿精仿京东商城手机客户端android版源码,是从安卓教程网那边转载过来的,这款应用源码非常不错的,也是一个非常优秀的应用源码的,希望能够帮到学习的朋友. _js_op> <igno ...

  4. Crackme006 - 全新160个CrackMe学习系列(图文|视频|注册机源码)

    知乎:逆向驿站 原文链接 CrackMe006 | 难度适中适合练手 |160个CrackMe深度解析(图文+视频+注册机源码) crackme006,依然是delphi的,而且没壳子,条线比较清晰, ...

  5. 教程视频、项目源码、全部干货【微信小程序、React Native、Java、iOS、数据结构】

    把我收藏多年的教学视频.项目源码分享给大家,大神就可以忽略了,很多东西都是基础性的,都是期初学习阶段收集的东西. 微信小程序(入门级,有web前端基础的人群): 链接: https://pan.bai ...

  6. 仿360手机卫士界面效果android版源码

    仿360手机卫士界面效果android版,这个今天一大早在源码天堂的那个网站上看到了一个那个网站最新更新的一个源码,所以就分享给大学习一下吧,布局还挺不错的,而且也很简单的,我就不把我修改的那个分享出 ...

  7. AndroidTv Home界面实现原理(二)——Leanback 库的主页卡位缩放动画源码解析

    先看个效果图: 上一篇中,我们留了问题,在 Tv Home 界面这种很常见聚焦卡位放大动画效果,我们这一篇就来看看 Leanback 库是怎么实现的. 如果要我们自己实现的话,思路应该不难,就是写个放 ...

  8. 仿哔哩哔哩应用客户端Android版源码项目

    这是一款高仿哔哩哔哩安卓客户端,跟官方网的差不多吧,界面也几乎是一样的,应用里面也加了一些弹出广告,大家可以参考一下吧,安装测试包在源码文件那里,大家可以多多参考一下. 哔哩哔哩弹幕网是国内知名的弹幕 ...

  9. 高仿it之家新闻客户端源码

    仿it之家新闻客户端界面,数据为本地假数据.仅实现了新闻模块的功能. 源码下载:http://code.662p.com/list/11_1.html 详细说明:http://android.662p ...

随机推荐

  1. 两个activity的3D翻转动画.md

    一.业务需求 这里在公司项目设计时,用到了一个小的需求,就是点击一个按钮然后整个activity的页面进行3d翻转; 二.设计思路 由于是2个activity的之间的翻转动画,就意味着前90度是A页面 ...

  2. Dev GridControl GridView常用属性

    1.隐藏最上面的GroupPanel: gridView1.OptionsView.ShowGroupPanel=false; 2.得到当前选定记录某字段的值: sValue=Table.Rows[g ...

  3. [LeetCode] Monotone Increasing Digits 单调递增数字

    Given a non-negative integer N, find the largest number that is less than or equal to N with monoton ...

  4. 使用python实现人脸检测

    人脸检测 人脸检测使用到的技术是OpenCV,上一节已经介绍了OpenCV的环境安装,点击查看. 功能展示 识别一种图上的所有人的脸,并且标出人脸的位置,画出人眼以及嘴的位置,展示效果图如下: 多张脸 ...

  5. [NOI 2010]超级钢琴

    Description 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙 的音乐. 这架超级钢琴可以弹奏出n个音符,编号为1至n.第i个音符的美妙 ...

  6. Conjugate

    1.1Conjugate问题描述在不存在的 noip day3 里,小 w ⻅到了一堆堆的谜题.比如这题为什么会叫共轭?他并不知道答案.有 n 堆谜题,每堆有 a i 个,小 w 每次从剩下的谜题中选 ...

  7. codesforces 671D Roads in Yusland

    Mayor of Yusland just won the lottery and decided to spent money on something good for town. For exa ...

  8. 【NOIP2012TG】solution

    D1T1(Vigenere) 题意:给你一个原串与一个密码串,问你按照题意规则加密后的密文. 解题思路:暴力模拟. #include <stdio.h> ],c[],u1[],u2[]; ...

  9. 【USACO】股票市场

    题目描述 尽管奶牛天生谨慎,它们仍然在住房抵押信贷市场中大受打击,现在它们准备在股市上碰碰运 气.贝西有内部消息,她知道 S 只股票在今后 D 天内的价格. 假设在一开始,她筹集了 M 元钱,那么她该 ...

  10. DP测试总结

    T1:三取方格数 题目描述 设有N*N的方格图,我们将其中的某些方格填入正整数,而其他的方格中放入0.某人从图得左上角出发,可以向下走,也可以向右走,直到到达右下角.在走过的路上,他取走了方格中的数. ...