First on the implementation of the effect map
First, TV development in the custom RecyclerView to solve several problems
1, fast sliding focus running problem
Using native RecyclerView in Android TV terminal will find that the focus is missing in the process of fast sliding, which is very strange. Through reading relevant source code, IT is found that Focus analysis of Android TV development summary [focus] I summarized two kinds of solutions in looking up other big god, for your reference 1, rewrite the focusSearch method of RecyclerView
@Override
public View focusSearch(View focused, int direction) {
View realNextFocus = super.focusSearch(focused, direction);
View nextFocus = FocusFinder.getInstance().findNextFocus(this, focused, direction);
switch (direction) {
case FOCUS_RIGHT:
case FOCUS_LEFT:
// Invoke the removed listener
if (nextFocus == null && !canScrollHorizontally(-1)) {
if (mCanFocusOutHorizontal) {
if(mFocusLostListener ! =null) {
mFocusLostListener.onFocusLost(focused, direction);
}
return realNextFocus;
} else {
returnfocused; }}break;
case FOCUS_UP:
case FOCUS_DOWN:
if (nextFocus == null && !canScrollVertically(1)) {
if (mCanFocusOutVertical) {
return realNextFocus;
} else {
returnfocused; }}break;
}
return realNextFocus;
}
Copy the code
2. Rewrite dispatchKeyEvent
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean result = super.dispatchKeyEvent(event);
View focusView = this.getFocusedChild();
if (focusView == null) {
return result;
}
int dy = 0;
int dx = 0;
if (getChildCount() > 0) {
View firstView = this.getChildAt(0);
dy = firstView.getHeight();
dx = firstView.getWidth();
}
if (event.getAction() == KeyEvent.ACTION_UP) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return super.dispatchKeyEvent(event);
}
return true;
} else {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_RIGHT:
View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);
Log.i(TAG, "rightView is null:" + (rightView == null));
if(rightView ! =null) {
rightView.requestFocus();
return true;
} else {
this.smoothScrollBy(dx, 0);
return true;
}
case KeyEvent.KEYCODE_DPAD_LEFT:
View leftView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);
Log.i(TAG, "leftView is null:" + (leftView == null));
if(leftView ! =null) {
leftView.requestFocus();
return true;
} else {
this.smoothScrollBy(-dx, 0);
return true;
}
case KeyEvent.KEYCODE_DPAD_DOWN:
View downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN);
Log.i(TAG, " downView is null:" + (downView == null));
if(downView ! =null) {
downView.requestFocus();
return true;
} else {
this.smoothScrollBy(0, dy);
return true;
}
case KeyEvent.KEYCODE_DPAD_UP:
View upView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_UP);
Log.i(TAG, "upView is null:" + (upView == null));
if(upView ! =null) {
upView.requestFocus();
return true;
} else {
this.smoothScrollBy(0, -dy);
return true; }}}return result;
}
Copy the code
2. Realize focus memory function
If you want to achieve focus memory function can rewrite the RecyclerView function as follows
private View mLastFocusView = null;
// Last focus position
private int mLastFocusPosition = 0;
@Override
public void requestChildFocus(View child, View focused) {
Log.i(TAG, "requestChildFocus nextchild= " + child + ",focused = " + focused);
Log.i(TAG, "requestChildFocus focusPos = " + mLastFocusPosition);
super.requestChildFocus(child, focused);
mLastFocusView = focused;
// hasFocus becomes true after super.requestChildFocus is executed
mLastFocusPosition = getChildViewHolder(child).getBindingAdapterPosition();
Log.i(TAG, "requestChildFocus focusPos = " + mLastFocusPosition);
}
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
Log.i(TAG, "addFocusables--focusPos = " + mLastFocusPosition);
if (this.hasFocus() || mLastFocusView == null) {
// Change the internal focus of recyclerView
super.addFocusables(views, direction, focusableMode);
} else {
// Place the current view in the Focusable Views list, and the view will be retrieved when the focus is moved againviews.add(getLayoutManager().findViewByPosition(mLastFocusPosition)); }}Copy the code
3. The focus of Item is not blocked
If you want to enlarge the Item without blocking it, you need to override getChildDrawingOrder
@Override
protected int getChildDrawingOrder(int childCount, int position) {
View focusedView = getFocusedChild();
if (null! = focusedView) {int pos = indexOfChild(focusedView);
/* This is the last item that needs to be refreshed
if (position == childCount - 1) {
if (pos > position) {
pos = position;
}
return pos;
}
else if (pos == position) {
/* This is the last item that was supposed to be refreshed */
return childCount - 1; }}return position;
}
Copy the code
4. Realize the sliding effect of Item center
If you want to realize the center sliding effect, there are two ways of 1, rewrite RecyclerView requestChildFocus and requestChildRectangleOnScreen method
// Whether the focus is centered
private boolean mSelectedItemCentered = true;
private int mSelectedItemOffsetStart = 0;
private int mSelectedItemOffsetEnd = 0;
@Override
public void requestChildFocus(View child, View focused) {
Log.i(TAG, "nextchild= " + child + ",focused = " + focused);
// Calculate control recyclerView selected item center sub parameter
if(mSelectedItemCentered && child ! =null) { mSelectedItemOffsetStart = ! isVertical() ? (getFreeWidth() - child.getWidth()) : (getFreeHeight() - child.getHeight()); mSelectedItemOffsetStart /=2;
mSelectedItemOffsetEnd = mSelectedItemOffsetStart;
}
Log.i(TAG, "requestChildFocus focusPos = " + mCurrentFocusPosition);
super.requestChildFocus(child, focused);
// hasFocus becomes true after super.requestChildFocus is executed
mCurrentFocusPosition = getChildViewHolder(child).getBindingAdapterPosition();
Log.i(TAG, "requestChildFocus focusPos = " + mCurrentFocusPosition);
}
/** * This method centers the selected item * * this method determines the position between the child item and the parent when scrolling or sliding in the layout * dy, The actual meaning of dx is the distance between sliding in a scroll and sliding left and right * and this value can seriously affect the smoothness of the slide */
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
Log.i(TAG, "requestChildRectangleOnScreen= " + child + ",pos = " + immediate);
final int parentLeft = getPaddingLeft();
final int parentRight = getWidth() - getPaddingRight();
final int parentTop = getPaddingTop();
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 - mSelectedItemOffsetStart);
final int offScreenRight = Math.max(0, childRight - parentRight + mSelectedItemOffsetEnd);
final int offScreenTop = Math.min(0, childTop - parentTop - mSelectedItemOffsetStart);
final int offScreenBottom = Math.max(0, childBottom - parentBottom + mSelectedItemOffsetEnd);
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.
final 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); }}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.
final int dy;
if(canScrollVertical) { dy = offScreenTop ! =0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom);
} else {
dy = 0;
}
if(dx ! =0|| dy ! =0) {
scrollBy(dx, dy);
if (immediate) {
scrollBy(dx, dy);
} else {
smoothScrollBy(dx, dy);
}
// Redraw to select item at the top. See getChildDrawingOrder for details
postInvalidate();
return true;
}
return false;
}
Copy the code
2. Customize LayoutManager to achieve center sliding
public class CenterLayoutManager extends LinearLayoutManager {
public CenterLayoutManager(Context context) {
super(context);
}
@Override
public void smoothScrollToPosition(final RecyclerView recyclerView, RecyclerView.State state,final int position) {
CenterSmoothScroller smoothScroller = new CenterSmoothScroller(recyclerView.getContext(),recyclerView) {
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
returncomputeVectorForPosition(targetPosition); }}; smoothScroller.setTargetPosition(position); startSmoothScroll(smoothScroller); }public PointF computeVectorForPosition(int targetPosition) {
return super.computeScrollVectorForPosition(targetPosition);
}
abstract class CenterSmoothScroller extends LinearSmoothScroller {
RecyclerView recyclerView;
CenterSmoothScroller(Context context,RecyclerView recyclerView) {
super(context);
this.recyclerView = recyclerView;
}
@Override
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
Log.i("du"."calculateDtToFit viewStart:" + viewStart + "---viewEnd:" + viewEnd + "---boxStart:" + boxStart + "---boxEnd:" + boxEnd + "----snapPreference:" + snapPreference );
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}
/** * after the slide completes, let the item at the targetPosition get focus */
@Override
protected void onStop(a) {
Log.i("du"."onStop-Position" + getTargetPosition());
super.onStop();
final View itemView = findViewByPosition(getTargetPosition());
if (null! = itemView) { itemView.requestFocus(); }}}}/ / recyclerview calls
@Override
public void onBindViewHolder(final @NonNull BaseViewHolder viewHolder, final int i) {
viewHolder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
Log.i("du"."onBindViewHolder-Position"+ i + "---hasFocus:" + hasFocus);
if (hasFocus) {
ViewCompat.animate(viewHolder.itemView).scaleX(1.5 f).scaleY(1.5 f).start();
mCenterLayoutManager.smoothScrollToPosition(mRecyclerView,new RecyclerView.State(), i);
} else {
ViewCompat.animate(viewHolder.itemView).scaleX(1f).scaleY(1f).start(); }}});Copy the code
Second, realize the wireless circular sliding of vertical list
Here refer to the online method in recyclerView. Adapter method:
@Override
public int getItemCount(a) {
return Integer.MAX_VALUE;
}
Copy the code
We need to adjust the logic to override the setAdapter since we need to center the wireless when we need to and default the positioning
@Override
public void setAdapter(@Nullable Adapter adapter) {
super.setAdapter(adapter);
// Locate to the corresponding position
scrollToPosition(((Integer.MAX_VALUE / 2) - ((Integer.MAX_VALUE / 2) % realCount)) + mCurPos);
postDelayed(new Runnable() {
@Override
public void run(a) {
View targetView = getLayoutManager().findViewByPosition(((Integer.MAX_VALUE/2)-((Integer.MAX_VALUE/2)%realCount)) + mCurPos);
if(targetView ! =null) { targetView.requestFocus(); }}},100);
}
Copy the code
Third, to achieve the effect of vertical list fading
Custom RecyclerView and rewrite the corresponding method
private Paint paint;
private int height;
private int width;
private int spanPixel = 100;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
height = h;
width = w;
float spanFactor = spanPixel / (height / 2f);
// Set fade to 0 0x00000000, spanFactor to 0xFF000000, and end to 0xFF000000
LinearGradient linearGradient = new LinearGradient(0.0.0, height / 2.new int[] {0x00000000.0xff000000.0xff000000}, new float[] {0, spanFactor, 1f}, Shader.TileMode.MIRROR);
paint.setShader(linearGradient);
}
@Override
public void draw(Canvas c) {
c.saveLayer(0.0, width, height, null, Canvas.ALL_SAVE_FLAG);
super.draw(c);
c.drawRect(0.0, width, height, paint);
c.restore();
}
Copy the code
Four, limit RecyclerView sliding speed
This can be used by adjusting the input interval of dispatchKeyEvent
private long mLastKeyDownTime;
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
long current = System.currentTimeMillis();
if(event.getAction() ! = KeyEvent.ACTION_DOWN || getChildCount() ==0) {
return super.dispatchKeyEvent(event);
}
// limit the minimum interval between two KEY_DOWN events to 120ms
if (isComputingLayout() || current - mLastKeyDownTime <= 120) {
return true;
}
mLastKeyDownTime = current;
return super.dispatchKeyEvent(event);
}
Copy the code