package com.wasu.widgets.focuswidget; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.view.ViewCompat; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.GridLayoutAnimationController; import com.wasu.widgets.tools.AnimTools; import com.wasu.widgets.tools.UIUtil; import cn.com.wasu.main.R; /** * Created by danxingxi on 2016-02-17. */ public class FocusRecyclerView extends RecyclerView implements IRecyclerView { /**预滚动的距离**/ private int scrollDy = getResources().getDimensionPixelSize(R.dimen.d_80dp); /**预移动的比例(默认1/2)**/ private double extraMove = 0.5; /**只对LinearLayoutManager保持焦点在中间**/ protected boolean canCenterScroll = false; /**焦点item放大倍数**/ protected float scaleValue = 1.06f; /**焦点框**/ Drawable focusShadowDrawable = null; /**获得焦点的item**/ View focusView = null; public FocusRecyclerView(Context context) { super(context); init(); } public FocusRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public FocusRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init(){ setChildrenDrawingOrderEnabled(true); setFocusable(false); setClipChildren(false); setClipToPadding(false); setWillNotDraw(true); setHasFixedSize(true); } public boolean isVertical() { if (getLayoutManager() instanceof LinearLayoutManager) { LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager(); return layout.getOrientation() == LinearLayoutManager.VERTICAL; } return true; } private int getFreeSize() { if(!isVertical()) { return getFreeHeight(); } else { return getFreeWidth(); } } private int getFreeHeight() { return getHeight() - getPaddingTop() - getPaddingBottom(); } private int getFreeWidth() { return getWidth() - getPaddingLeft() - getPaddingRight(); } public void setFocusShadowDrawable(Drawable focusShadowDrawable) { this.focusShadowDrawable = focusShadowDrawable; } /* /** * center scroll * @param child * @param rect * @param immediate * @return *//* @Override public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { final int parentLeft = getPaddingLeft(); final int parentTop = getPaddingTop(); final int parentRight = getWidth() - getPaddingRight(); final int parentBottom = getHeight() - getPaddingBottom(); final int childLeft = child.getLeft() + rect.left; final int childTop = child.getTop() + rect.top; final int childRight = childLeft + rect.width(); final int childBottom = childTop + rect.height(); final int offScreenLeft = Math.min(0, childLeft - parentLeft); final int offScreenTop = Math.min(0, childTop - parentTop); final int offScreenRight = Math.max(0, childRight - parentRight); final int offScreenBottom = Math.max(0, childBottom - parentBottom); int centerTop = getHeight() / 2 - child.getHeight() / 2; int centerBottom = getHeight() / 2 + child.getHeight() / 2; int centerLeft = getWidth() / 2 - child.getWidth() / 2; int centerRight = getWidth() / 2 + child.getWidth() / 2; int position = getChildAdapterPosition(child); int childCount = getAdapter().getItemCount(); int dx = 0; if (position == 0 || position == childCount - 1){ // Favor the "start" layout direction over the end when bringing one side or the other // of a large rect into view. If we decide to bring in end because start is already // visible, limit the scroll such that start won't go out of bounds. if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) { dx = offScreenRight != 0 ? offScreenRight : Math.max(offScreenLeft, childRight - parentRight); } else { dx = offScreenLeft != 0 ? offScreenLeft : Math.min(childLeft - parentLeft, offScreenRight); } }else { switch (Gravity.CENTER_HORIZONTAL){ case Gravity.CENTER_HORIZONTAL: case Gravity.CENTER: dx = childLeft - centerLeft; break; case Gravity.LEFT: dx = childLeft - parentLeft; break; } } int dy = 0; if (position == 0 || position == childCount - 1){ // Favor bringing the top into view over the bottom. If top is already visible and // we should scroll to make bottom visible, make sure top does not go out of bounds. dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom); }else { switch (gravity){ case Gravity.CENTER_VERTICAL: case Gravity.CENTER: dy = childTop - centerTop; break; } } if (dx != 0 || dy != 0) { if (immediate) { scrollBy(dx, dy); } else { smoothScrollBy(dx, dy); } return true; } return false; }*/ @Override public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { final int parentLeft = getPaddingLeft(); final int parentTop = getPaddingTop(); final int parentRight = getWidth() - getPaddingRight(); final int parentBottom = getHeight() - getPaddingBottom(); final int childLeft = child.getLeft() + rect.left; final int childTop = child.getTop() + rect.top; final int childRight = childLeft + rect.width(); final int childBottom = childTop + rect.height(); final int offScreenLeft = Math.min(0, childLeft - parentLeft ); final int offScreenTop = Math.min(0, childTop - parentTop ); final int offScreenRight = Math.max(0, childRight - parentRight ); final int offScreenBottom = Math.max(0, childBottom - parentBottom ); final boolean canScrollHorizontal = getLayoutManager().canScrollHorizontally(); final boolean canScrollVertical = getLayoutManager().canScrollVertically(); // Favor the "start" layout direction over the end when bringing one side or the other // of a large rect into view. If we decide to bring in end because start is already // visible, limit the scroll such that start won't go out of bounds. int dx; if(canScrollHorizontal) { if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) { dx = offScreenRight != 0 ? offScreenRight : Math.max(offScreenLeft, childRight - parentRight); } else { dx = offScreenLeft != 0 ? offScreenLeft : Math.min(childLeft - parentLeft, offScreenRight); } //增加预滚动 if(dx > 0){ dx += dx*extraMove; }else if(dx <0) { dx += dx*extraMove; } } else { dx = 0; } // Favor bringing the top into view over the bottom. If top is already visible and // we should scroll to make bottom visible, make sure top does not go out of bounds. int dy; if(canScrollVertical) { dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom); //增加预滚动 if(dy > 0){ dy += dy*extraMove; }else if(dy <0) { dy += dy*extraMove; } } else { dy = 0; } if (dx != 0 || dy != 0) { if (immediate) { scrollBy(dx, dy); } else { smoothScrollBy(dx, dy); } return true; } // 重绘是为了选中item置顶,具体请参考getChildDrawingOrder方法 postInvalidate(); return false; } public int getFirstVisiblePosition() { if(getChildCount() == 0) return 0; else return getChildLayoutPosition(getChildAt(0)); } public int getLastVisiblePosition() { final int childCount = getChildCount(); if(childCount == 0) return 0; else return getChildLayoutPosition(getChildAt(childCount - 1)); } @Override protected void attachLayoutAnimationParameters(View child, ViewGroup.LayoutParams params, int index, int count) { if (getAdapter() != null && getLayoutManager() instanceof GridLayoutManager){ GridLayoutAnimationController.AnimationParameters animationParams = (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters; if (animationParams == null) { animationParams = new GridLayoutAnimationController.AnimationParameters(); params.layoutAnimationParameters = animationParams; } int columns = ((GridLayoutManager) getLayoutManager()).getSpanCount(); animationParams.count = count; animationParams.index = index; animationParams.columnsCount = columns; animationParams.rowsCount = count / columns; final int invertedIndex = count - 1 - index; animationParams.column = columns - 1 - (invertedIndex % columns); animationParams.row = animationParams.rowsCount - 1 - invertedIndex / columns; } else { super.attachLayoutAnimationParameters(child, params, index, count); } } @Override public void modifyUI(View focusView) { this.focusView = focusView; postInvalidate(); } /*** * 更新UI并重绘 * @param focusView 焦点Item * @param position Item的位置 */ @Override public void modifyUI(View focusView, int position) { this.focusView = focusView; postInvalidate(); } /** * 画焦点框,由于recyclerView的滚动会导致这个方法重复调用,焦点绘制也会存在过渡绘制 * @param canvas */ @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (focusView != null) { if (focusShadowDrawable == null) { focusShadowDrawable = getContext().getResources().getDrawable(R.drawable.focus_border); } // Rect toFocusedViewRect = UIUtil.createViewRect(this, focusView.findViewById(R.id.img_view), 0); Rect toFocusedViewRect = UIUtil.createViewRect(this, focusView, 0); UIUtil.drawDrawableAt(canvas, toFocusedViewRect, focusShadowDrawable); } } @Override public boolean dispatchKeyEvent(KeyEvent event) { if(focusView != null){ int itemCount = getLayoutManager().getItemCount(); if(itemViewFocusSearchListener != null){ if(itemViewFocusSearchListener.onItemViewFocusSearch(this, focusView, itemCount, getChildAdapterPosition(focusView), event)){ return true; } } } return super.dispatchKeyEvent(event); } int position = 0; @Override protected int getChildDrawingOrder(int childCount, int i) { View view = getFocusedChild(); if(null != view) { position = getChildAdapterPosition(view) - getFirstVisiblePosition(); if (position < 0) { return i; } else { if (i == childCount - 1) {//这是最后一个需要刷新的item if (position > i) { position = i; } return position; } if (i == position) {//这是原本要在最后一个刷新的item return childCount - 1; } } } return i; } public boolean isScrolling() { return getScrollState() == SCROLL_STATE_SETTLING; } private ItemViewFocusSearchListener itemViewFocusSearchListener; public void setItemViewFocusSearchListener(ItemViewFocusSearchListener itemViewFocusSearchListener){ this.itemViewFocusSearchListener = itemViewFocusSearchListener; } /** * 当焦点item在边缘时方便控制焦点分发 */ public interface ItemViewFocusSearchListener{ boolean onItemViewFocusSearch(FocusRecyclerView parent, View itemView, int itemCount, int position, KeyEvent event); } @Override public boolean isInTouchMode() { boolean result = super.isInTouchMode(); // 解决4.4版本抢焦点的问题 if (Build.VERSION.SDK_INT == 19) { return !(hasFocus() && !result); } else { return result; } } /** * itemView晃动动画 * @param view * @return */ @Override public void shockAnim(View view){ this.focusView=view; ObjectAnimator animator= AnimTools.shock(focusView); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { postInvalidate(); } }); animator.start(); } @Override public void setItemViewFocusSearchListener(com.wasu.widgets.focuswidget.ItemViewFocusSearchListener itemViewFocusSearchListener) { } /** * 修改移动的距离(增加百分比) * */ public void setExtraMove(double extraMove) { this.extraMove = extraMove; } }