androidandroid-tvleanback

disable the scroll effect of the action list


I've got a GuidedStepSupportFragment fragment like this.

public class SampleStepFragment extends GuidedStepSupportFragment {

    @NonNull
    @Override
    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
        String title = "Title";
        String breadcrumb = "Breadcrumb";
        String description = "Description";
        Drawable icon = getActivity().getDrawable(R.drawable.ic_videocam_black_24dp);

        return new GuidanceStylist.Guidance(title, description, breadcrumb, icon);
    }

    @Override
    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {

        addAction(actions, ACTION_CONTINUE, "Action1");
        addAction(actions, ACTION_BACK, "Action2");

    }
}

action1

Problem: When I scroll the action list, it shows like this;

action1

But I want to something like this;

expected

How can I disable this effect on my action list?

Thanks


Solution

  • I managed it and it wasn't easy to figure out.

    There's no supported way of doing it, since the APIs that actually make this possible are package private or hidden from public use on purpose. (You can do it yourself, but you just end up copying classes from the leanback libraries.)

    The solution:

    @Override
    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
    
        addAction(actions, GuidedAction.ACTION_ID_CONTINUE, "Action1");
        addAction(actions, GuidedAction.ACTION_ID_CANCEL, "Action2");
    
        // Run code delayed on mainThread (any other/better method can/should be used)
        // It's delayed because if focus scroll is disabled, the list will stick to the top of the layout
        new Handler(Looper.getMainLooper()).postDelayed(this::disableFocusScroll, 500);
    }
    
    private void disableFocusScroll() {
        RecyclerView.LayoutManager layoutManager = SampleStepFragment.this.getGuidedActionsStylist().getActionsGridView().getLayoutManager();
        try {
            Method method = layoutManager.getClass().getMethod("setFocusScrollStrategy", int.class);
            method.invoke(layoutManager, 1 /* FOCUS_SCROLL_ITEM */);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            Log.e(TAG, "disableFocusScroll: ", e);
        }
    }
    

    Full example

    The explanation:

    A GuidedStepSupportFragment requests a GuidedActionsStylist which is responsible for rendering the list items on the right side. source

    The GuidedActionsStylist stylist inflates the layout lb_guidedactions.xml which contains a VerticalGridView source

    The VerticalGridView extends BaseGridView and creates a GridLayoutManager as its layout manager. This GridLayoutManager is sadly package private and final... (android why..?). It has the method setFocusScrollStrategy which is used to determine how scrolling behaves.source

    See the different focus scroll strategies:

    /**
     * Always keep focused item at a aligned position.  Developer can use
     * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned.
     * In this mode, the last focused position will be remembered and restored when focus
     * is back to the view.
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    public final static int FOCUS_SCROLL_ALIGNED = 0;
    
    /**
     * Scroll to make the focused item inside client area.
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    public final static int FOCUS_SCROLL_ITEM = 1;
    
    /**
     * Scroll a page of items when focusing to item outside the client area.
     * The page size matches the client area size of RecyclerView.
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    public final static int FOCUS_SCROLL_PAGE = 2;
    

    So since the API is hidden, we just use reflection to expose the setFocusScrollStrategy method and set it to FOCUS_SCROLL_ITEM.

    We can't do this immediately though, since without the default scroll setting, the list items will pop to the top of the layout and won't stay centered. So I added a delay of 500ms which is horrible... If you manage to find out when it's best to trigger this, let me know.