1. 程式人生 > >TV Metro介面(仿泰捷視訊TV版)原始碼解析

TV Metro介面(仿泰捷視訊TV版)原始碼解析

先看下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;