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.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.FocusFinder; import android.view.KeyEvent; import android.view.View; import com.wasu.widgets.tools.AnimTools; import com.wasu.widgets.tools.UIUtil; import cn.com.wasu.main.R; /** * SpanFocusRecyclerView * @Description: 也是绘制了焦点的recyclerView,但是此空间可以在adapter的onCreateViewHolder方法中 * 设置itemView所占的行数和列数,本空间不支持用代码设置adapter,只能在xml文件中给出 * @Author: Danxingxi * @CreateDate: 2016/8/16 21:14 */ public class TvRecyclerView extends RecyclerView implements IRecyclerView { private static final String TAG = TvRecyclerView.class.getSimpleName(); /**焦点框**/ Drawable focusShadowDrawable = null; /**获得焦点的item**/ View focusView = null; /**优化TvRecyclerView在滚动时焦点的绘制*/ private View canvasView; /**优化TvRecyclerView在滚动时焦点的绘制次数*/ int drawCount = 1; public TvRecyclerView(Context context) { this(context, null); } public TvRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TvRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context){ setChildrenDrawingOrderEnabled(true); setFocusable(false); setClipChildren(false); setClipToPadding(false); setWillNotDraw(true); setHasFixedSize(true); addItemDecoration(new ItemDecoration() { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { super.getItemOffsets(outRect, view, parent, state); // outRect.left = getResources().getDimensionPixelSize(com.wasu.module.wechattv.R.dimen.d_16dp); outRect.left= getResources().getDimensionPixelSize(R.dimen.d_12dp); outRect.top = getResources().getDimensionPixelSize(R.dimen.d_8dp); } }); } 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(); } /* /** * 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/3; }else if(dx <0) { dx += dx/3; } } 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); //增加预滚动 } 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 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(isScrolling()){ if(canvasView!=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, canvasView, 0); UIUtil.drawDrawableAt(canvas, toFocusedViewRect, focusShadowDrawable); if(drawCount>=2){ canvasView = null; drawCount = 1; }else{ ++drawCount; } return; } } if (focusShadowDrawable == null) { focusShadowDrawable = getContext().getResources().getDrawable(R.drawable.focus_border); } // canvasView = focusView; // Rect toFocusedViewRect = UIUtil.createViewRect(this, focusView.findViewById(R.id.img_view), 0); Rect toFocusedViewRect = UIUtil.createViewRect(this, focusView, 0); UIUtil.drawDrawableAt(canvas, toFocusedViewRect, focusShadowDrawable); } } 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; @Override public void setItemViewFocusSearchListener(ItemViewFocusSearchListener itemViewFocusSearchListener){ this.itemViewFocusSearchListener = itemViewFocusSearchListener; } @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; } } } switch (event.getAction()) { case KeyEvent.ACTION_DOWN: if(onKeyDown(event.getKeyCode(), event)) { return true; } break; case KeyEvent.ACTION_UP: if(onKeyUp(event.getKeyCode(), event)) { return true; } break; } return super.dispatchKeyEvent(event); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { int direction = -1; switch (keyCode){ case KeyEvent.KEYCODE_DPAD_DOWN: direction = FOCUS_DOWN; break; case KeyEvent.KEYCODE_DPAD_RIGHT: direction = FOCUS_RIGHT; break; case KeyEvent.KEYCODE_DPAD_LEFT: direction = FOCUS_LEFT; break; case KeyEvent.KEYCODE_DPAD_UP: direction = FOCUS_UP; break; } if(direction == -1 || hasInBorder(direction)) { return false; } else { FocusFinder ff = FocusFinder.getInstance(); View newFocusedView = ff.findNextFocus(this, getFocusedChild(), direction); if (null != newFocusedView) { newFocusedView.requestFocus(); } } return true; } /** * 是否到了边界 * @param direction * @return */ private boolean hasInBorder(int direction) { boolean result = false; final View view = getFocusedChild(); if(null != view) { Rect outRect = new Rect(); LayoutParams lp = (LayoutParams) view.getLayoutParams(); getLayoutManager().calculateItemDecorationsForChild(view, outRect); switch (direction) { case FOCUS_DOWN: result = getHeight() - view.getBottom() <= getPaddingBottom() + lp.bottomMargin + outRect.bottom; if(isVertical()) { result = result && getLastVisiblePosition() == (getAdapter().getItemCount() - 1); } break; case FOCUS_UP: result = view.getTop() <= getPaddingTop() + lp.topMargin + outRect.top; if(isVertical()) { result = result && getFirstVisiblePosition() == 0; } break; case FOCUS_LEFT: result = view.getLeft() <= getPaddingLeft() + lp.leftMargin + outRect.left; if(!isVertical()) { result = result && getFirstVisiblePosition() == 0; } break; case FOCUS_RIGHT: result = getWidth() - view.getRight() <= getPaddingRight() + lp.rightMargin + outRect.right; if(!isVertical()) { result = result && getLastVisiblePosition() == (getAdapter().getItemCount() - 1); } break; } } return result; } @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(); } }