Last active
December 15, 2022 03:07
-
-
Save log2c/28c3a0889219c78cc156f78e6395cbda to your computer and use it in GitHub Desktop.
Android TV列表焦点选中效果 '''mMetroViewBorderImpl.attachTo(recyclerView)'''
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.support.v7.widget.GridLayoutManager; | |
import android.support.v7.widget.RecyclerView; | |
import android.util.AttributeSet; | |
import android.view.View; | |
public class AutoLayoutManager extends GridLayoutManager { | |
public AutoLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
super(context, attrs, defStyleAttr, defStyleRes); | |
} | |
public AutoLayoutManager(Context context, int spanCount) { | |
super(context, spanCount); | |
} | |
public AutoLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { | |
super(context, spanCount, orientation, reverseLayout); | |
} | |
@Override | |
public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, | |
RecyclerView.State state) { | |
// Need to be called in order to layout new row/column | |
View nextFocus = super.onFocusSearchFailed(focused, focusDirection, recycler, state); | |
if (nextFocus == null) { | |
return null; | |
} | |
int fromPos = getPosition(focused); | |
int nextPos = getNextViewPos(fromPos, focusDirection); | |
return findViewByPosition(nextPos); | |
} | |
/** | |
* Manually detect next view to focus. | |
* | |
* @param fromPos from what position start to seek. | |
* @param direction in what direction start to seek. Your regular | |
* {@code View.FOCUS_*}. | |
* @return adapter position of next view to focus. May be equal to | |
* {@code fromPos}. | |
*/ | |
protected int getNextViewPos(int fromPos, int direction) { | |
int offset = calcOffsetToNextView(direction); | |
if (hitBorder(fromPos, offset)) { | |
//return fromPos; | |
} | |
return fromPos + offset; | |
} | |
/** | |
* Calculates position offset. | |
* | |
* @param direction regular {@code View.FOCUS_*}. | |
* @return position offset according to {@code direction}. | |
*/ | |
protected int calcOffsetToNextView(int direction) { | |
int spanCount = getSpanCount(); | |
int orientation = getOrientation(); | |
if (orientation == VERTICAL) { | |
switch (direction) { | |
case View.FOCUS_DOWN: | |
return spanCount; | |
case View.FOCUS_UP: | |
return -spanCount; | |
case View.FOCUS_RIGHT: | |
return 1; | |
case View.FOCUS_LEFT: | |
return -1; | |
} | |
} else if (orientation == HORIZONTAL) { | |
switch (direction) { | |
case View.FOCUS_DOWN: | |
return 1; | |
case View.FOCUS_UP: | |
return -1; | |
case View.FOCUS_RIGHT: | |
return spanCount; | |
case View.FOCUS_LEFT: | |
return -spanCount; | |
} | |
} | |
return 0; | |
} | |
/** | |
* Checks if we hit borders. | |
* | |
* @param from from what position. | |
* @param offset offset to new position. | |
* @return {@code true} if we hit border. | |
*/ | |
private boolean hitBorder(int from, int offset) { | |
int spanCount = getSpanCount(); | |
if (Math.abs(offset) == 1) { | |
int spanIndex = from % spanCount; | |
int newSpanIndex = spanIndex + offset; | |
return newSpanIndex < 0 || newSpanIndex >= spanCount; | |
} else { | |
int newPos = from + offset; | |
return newPos < 0 || newPos >= spanCount; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.view.View; | |
public interface IMetroViewBorder { | |
void onFocusChanged(View target, View oldFocus, View newFocus); | |
void onScrollChanged(View target, View attachView); | |
void onLayout(View target, View attachView); | |
void onTouchModeChanged(View target, View attachView, boolean isInTouchMode); | |
void onAttach(View target, View attachView); | |
void OnDetach(View target, View view); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.animation.Animator; | |
import android.animation.AnimatorSet; | |
import android.animation.ObjectAnimator; | |
import android.animation.PropertyValuesHolder; | |
import android.animation.ValueAnimator; | |
import android.graphics.Rect; | |
import android.support.v7.widget.RecyclerView; | |
import android.util.Log; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.view.animation.DecelerateInterpolator; | |
import android.widget.AbsListView; | |
import android.widget.AdapterView; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
@SuppressWarnings({"unchecked", "unused"}) | |
public class MetroViewBorderHandler implements IMetroViewBorder { | |
private static final String TAG = MetroViewBorderHandler.class.getSimpleName(); | |
protected boolean mScalable = true;//是否缩小 | |
protected float mScale = 1.1f;//设置放大比例 | |
protected long mDurationTranslate = 200;//焦点移动的动画时间 | |
protected int mMargin = 0; | |
protected View lastFocus, oldLastFocus;//上一个焦点,新的位置的焦点 | |
protected AnimatorSet mAnimatorSet;//动画集合,可组合多个动画 | |
protected List<Animator> mAnimatorList = new ArrayList<>(); | |
protected View mTarget; | |
protected boolean mEnableTouch = true; | |
public MetroViewBorderHandler() { | |
mFocusListener.add(mFocusMoveListener);//设置焦点移动时监听 | |
mFocusListener.add(mFocusScaleListener);//设置焦点放大缩小监听 | |
mFocusListener.add(mFocusPlayListener); | |
mFocusListener.add(mAbsListViewFocusListener); | |
} | |
public interface FocusListener { | |
void onFocusChanged(View oldFocus, View newFocus); | |
} | |
protected List<FocusListener> mFocusListener = new ArrayList<>(1); | |
protected List<Animator.AnimatorListener> mAnimatorListener = new ArrayList<>(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(mDurationTranslate);//设置动画时间 | |
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 = 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<>(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.contains(oldFocus) && attacheViews.contains(newFocus)) { | |
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 = 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<>(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(mDurationTranslate); | |
mAnimatorSet.playTogether(animatorList); | |
mAnimatorSet.start(); | |
oldOn.oldFocus = null; | |
oldOn.newFocus = null; | |
newOn.oldFocus = null; | |
newOn.newFocus = b.getSelectedView(); | |
} | |
} | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
} | |
}; | |
protected List<Animator> getScaleAnimator(View view, boolean isScale) { | |
List<Animator> animatorList = new ArrayList<>(2); | |
if (!mScalable) return animatorList;//如果没有放大,直接返回 | |
try { | |
float scaleBefore = 1.0f;//放大前比例 | |
float scaleAfter = mScale;//放大后比例 | |
if (!isScale) {// | |
scaleBefore = mScale; | |
scaleAfter = 1.0f; | |
} | |
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", scaleBefore, scaleAfter); | |
ObjectAnimator scaleY = 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<>(); | |
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 valuesWidthHolder = 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, valuesWidthHolder, 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 static 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.contains(oldFocus) && attacheViews.contains(newFocus)) { | |
return scope; | |
} | |
if (oldFocus != null && newFocus != null) { | |
if (oldFocus.getParent() != newFocus.getParent()) { | |
if ((!attacheViews.contains(newFocus.getParent())) || (!attacheViews.contains(oldFocus.getParent()) && 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.contains(oldFocus.getParent())) { | |
scope.oldFocus = null; | |
} | |
} else { | |
if (!attacheViews.contains(newFocus.getParent())) { | |
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.contains(newFocus)) { | |
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; | |
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(mDurationTranslate); | |
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<>(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(mDurationTranslate); | |
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.app.Activity; | |
import android.content.Context; | |
import android.os.Build; | |
import android.util.AttributeSet; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.view.ViewTreeObserver; | |
@SuppressWarnings({"unchecked", "unused"}) | |
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 && viewGroup != null) { | |
ViewTreeObserver viewTreeObserver = viewGroup.getViewTreeObserver(); | |
if (viewTreeObserver != null && 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(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment