TV Metro界面(仿泰捷视频TV版)源码解析
转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼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版)源码解析的更多相关文章
- Android TV开发总结(二)构建一个TV Metro界面(仿泰捷视频TV版)
前言:上篇是介绍构建TV app前要知道的一些事儿,开发Android TV和手机本质上没有太大的区别,屏大,焦点处理,按键处理,是有别于有手机和Pad的实质区别.今天来介绍TV中Metro UI风格 ...
- Android TV开发总结(四)通过RecycleView构建一个TV app列表页(仿腾讯视频TV版)
转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52854131 前言:昨晚看锤子手 ...
- 完美高仿精仿京东商城手机客户端android版源码
完美高仿精仿京东商城手机客户端android版源码,是从安卓教程网那边转载过来的,这款应用源码非常不错的,也是一个非常优秀的应用源码的,希望能够帮到学习的朋友. _js_op> <igno ...
- Crackme006 - 全新160个CrackMe学习系列(图文|视频|注册机源码)
知乎:逆向驿站 原文链接 CrackMe006 | 难度适中适合练手 |160个CrackMe深度解析(图文+视频+注册机源码) crackme006,依然是delphi的,而且没壳子,条线比较清晰, ...
- 教程视频、项目源码、全部干货【微信小程序、React Native、Java、iOS、数据结构】
把我收藏多年的教学视频.项目源码分享给大家,大神就可以忽略了,很多东西都是基础性的,都是期初学习阶段收集的东西. 微信小程序(入门级,有web前端基础的人群): 链接: https://pan.bai ...
- 仿360手机卫士界面效果android版源码
仿360手机卫士界面效果android版,这个今天一大早在源码天堂的那个网站上看到了一个那个网站最新更新的一个源码,所以就分享给大学习一下吧,布局还挺不错的,而且也很简单的,我就不把我修改的那个分享出 ...
- AndroidTv Home界面实现原理(二)——Leanback 库的主页卡位缩放动画源码解析
先看个效果图: 上一篇中,我们留了问题,在 Tv Home 界面这种很常见聚焦卡位放大动画效果,我们这一篇就来看看 Leanback 库是怎么实现的. 如果要我们自己实现的话,思路应该不难,就是写个放 ...
- 仿哔哩哔哩应用客户端Android版源码项目
这是一款高仿哔哩哔哩安卓客户端,跟官方网的差不多吧,界面也几乎是一样的,应用里面也加了一些弹出广告,大家可以参考一下吧,安装测试包在源码文件那里,大家可以多多参考一下. 哔哩哔哩弹幕网是国内知名的弹幕 ...
- 高仿it之家新闻客户端源码
仿it之家新闻客户端界面,数据为本地假数据.仅实现了新闻模块的功能. 源码下载:http://code.662p.com/list/11_1.html 详细说明:http://android.662p ...
随机推荐
- Python求解啤酒问题(携程2016笔试题)
问题描述:一位酒商共有5桶葡萄酒和1桶啤酒,6个桶的容量分别为30升.32升.36升.38升.40升和62升,并且只卖整桶酒,不零卖.第一位顾客买走了2整桶葡萄酒,第二位顾客买走的葡萄酒是第一位顾客的 ...
- [LeetCode] Next Greater Element III 下一个较大的元素之三
Given a positive 32-bit integer n, you need to find the smallest 32-bit integer which has exactly th ...
- HTTP你真的懂了吗?
最近面试踩了些坑,自己看书看过的内容,即使能记得差不多,回答起来就是很混乱(绝望脸).比如HTTP的这几个问题,现在整理一下,一个点一个点的说! 1. 聊一聊你理解的HTTP 1) Http ...
- python学习记录2
一.两个模块(sys和os) #!/usr/bin/env python # _*_ coding: UTF-8 _*_ # Author:taoke import sys print(sys.pat ...
- Codeforces 812E Sagheer and Apple Tree
大致题意: 给你一颗树,这个树有下列特征:每个节点上有若干个苹果,且从根节点到任意叶子节点的路径长度奇偶性相同. 甲和乙玩(闲)游(得)戏(慌). 游戏过程中,甲乙轮流将任意一个节点的若干个苹果移向它 ...
- UVA - 11107:Life Forms
后缀数组height+二分 #include<cstdio> #include<cstdlib> #include<algorithm> #include<c ...
- 【NOIP2016TG】solution
传送门:https://www.luogu.org/problem/lists?name=&orderitem=pid&tag=83%7C33 D1T1(toys) 题意:有n个小人, ...
- CTSC&APIO2017
CTSC Day -1 因为越发感到自己与dalao们之间姿势水平的差距,本来打算再多学些姿势,但被老师叫去做noi,于是花了一两周的时间做完了noi2011~2015,也学到了一些奇怪姿势,还是挺有 ...
- ●UOJ 131 [NOI2015] 品酒大会
题链: http://uoj.ac/problem/131 题解: 网上大多数的方法都是用并查集维护.这里呢,给出另一种自己YY的解法(但实际上本质差不多吧): 后缀数组,RMQ,单调栈 1).预处理 ...
- poj 3264 & poj 3468(线段树)
poj 3264 Sample Input 6 3 1 7 3 4 2 5 1 5 4 6 2 2 Sample Output 6 3 0 求任一区间的最大值和最小值的差 #include<io ...