2

Is it possible to use multiple SearchOrb buttons or another focusable buttons in custom title view of leanback showcase app? The problem is that I can focus only one button...

UPDATE:

What I've done:

There is custom title view class, which contains Search Orb, title text view, Custom Orb and Analog clock.

The problem is that when I try to focus Search Orb, the focus goes to custom Orb... And that's all. Focuses only Custom Orb.

Click Listener for main Search Orb is set. I don't know where the problem is. If it is needed I can post other code on demand.

Thanks for helping!

titleview.xml:

<?xml version="1.0" encoding="utf-8"?>
    <com.olegmart.smart.base.CustomTitleView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/browse_title_group"
    android:padding="16dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

CustomTitleView.java:

package com.olegmart.smart.base;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.TitleViewAdapter;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.ImageView;

import com.olegmart.smart.R;

public class CustomTitleView extends RelativeLayout implements TitleViewAdapter.Provider {
    private final TextView mTitleView;
    private final View mAnalogClockView;
    private final View mGridOrbView;
    private final View mSearchOrbView;

private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
    @Override
    public View getSearchAffordanceView() {
        return mSearchOrbView;
    }

    @Override
    public void setTitle(CharSequence titleText) {
        CustomTitleView.this.setTitle(titleText);
    }

    @Override
    public void setBadgeDrawable(Drawable drawable) {
        //CustomTitleView.this.setBadgeDrawable(drawable);
    }

    @Override
    public void updateComponentsVisibility(int flags) {
        /*if ((flags & BRANDING_VIEW_VISIBLE) == BRANDING_VIEW_VISIBLE) {
            updateBadgeVisibility(true);
        } else {
            mAnalogClockView.setVisibility(View.GONE);
            mTitleView.setVisibility(View.GONE);
        }*/

        int visibility = (flags & SEARCH_VIEW_VISIBLE) == SEARCH_VIEW_VISIBLE
                ? View.VISIBLE : View.INVISIBLE;
        mSearchOrbView.setVisibility(visibility);
    }

    private void updateBadgeVisibility(boolean visible) {
        if (visible) {
            mAnalogClockView.setVisibility(View.VISIBLE);
            mGridOrbView.setVisibility(View.VISIBLE);
            mTitleView.setVisibility(View.VISIBLE);

        } else {
            mAnalogClockView.setVisibility(View.GONE);
            mGridOrbView.setVisibility(View.GONE);
            mTitleView.setVisibility(View.GONE);
        }
    }
};

public CustomTitleView(Context context) {
    this(context, null);
}

public CustomTitleView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public CustomTitleView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    View root  = LayoutInflater.from(context).inflate(R.layout.custom_titleview, this);
    mTitleView = (TextView) root.findViewById(R.id.title_tv);
    mAnalogClockView = root.findViewById(R.id.clock);
    mGridOrbView = root.findViewById(R.id.grid_orb);

    mSearchOrbView = root.findViewById(R.id.search_orb);
}

public void setTitle(CharSequence title) {
    if (title != null) {
        mTitleView.setText(title);
        mTitleView.setVisibility(View.VISIBLE);
        mGridOrbView.setVisibility(View.VISIBLE);
        mAnalogClockView.setVisibility(View.VISIBLE);
    }
}


public void setBadgeDrawable(Drawable drawable) {
    if (drawable != null) {
        mTitleView.setVisibility(View.GONE);
        mGridOrbView.setVisibility(View.VISIBLE);
        mAnalogClockView.setVisibility(View.VISIBLE);
    }
}

@Override
public TitleViewAdapter getTitleViewAdapter() {
    return mTitleViewAdapter;
}
}

custom_titleview.xml:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <android.support.v17.leanback.widget.SearchOrbView
        android:id="@+id/search_orb"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:transitionGroup="true"
        android:layout_gravity="center_vertical|start"
        android:layout_marginTop="8dp"
        android:paddingStart="48dp"
        android:focusable="true"
        android:nextFocusRight="@+id/grid_orb"
        />

    <AnalogClock
            android:id="@+id/clock"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:padding="6dp"
            android:layout_alignParentEnd="true"
            android:layout_gravity="center_vertical|end"
            android:layout_marginEnd="24dp" />

    <android.support.v17.leanback.widget.SearchOrbView
        android:id="@+id/grid_orb"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="24dp"
        android:layout_centerVertical="true"
        android:layout_toStartOf="@id/clock"
        android:nextFocusLeft="@+id/search_orb"
        android:focusable="true"
        />



    <TextView
            android:id="@+id/title_tv"
            android:textAppearance="@android:style/TextAppearance.Large"
            android:visibility="gone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="24dp"
            android:layout_centerVertical="true"
            android:layout_toStartOf="@id/grid_orb" />

</merge>
Neil Turner
  • 2,712
  • 2
  • 18
  • 37
demogorgorn
  • 306
  • 1
  • 4
  • 12
  • What have you done so far? Can you post your code and error message that you are encountering. – Mr.Rebot Jul 06 '16 at 14:53
  • Added code. Thank you for helping! Please note that there are no error codes - all is working fine, but buttons except one are not focused. – demogorgorn Jul 06 '16 at 18:15

3 Answers3

9

Teng-pao Yu is correct....the OnFocusSearchListener in BrowseFragment is the reason why focus changes are not occurring as you would expect.

A little background first...

When a focus-search occurs it will first invoke the focusSearch method in the current ViewGroup. In this case, while the search orb has the current focus, and the user presses the d-pad to focus in a different direction, the first callback to focus-search will be in your CustomTitleView class. It's only after CustomTitleView propagates focusSearch to the parent that BrowseFragment's OnFocusSearchListener is invoked.

The real issue here is (at the time of writing this), the Leanback BrowseFragment's OnFocusSearchListener is coded so that other views you add to your CustomTitleLayout will never actually be able to take focus.

However, there is a way to focus other child views in your CustomTitleLayout....

Override the focusSearch method in your CustomTitleLayout as shown in the code-sample below and return a focusable view that exists inside your custom title layout (as opposed to returning to super.focusSearch).

Note this code is dependent upon setting the nextFocusLeft and nextFocusRight attributes on your CustomTitleView's children. Also, the child views must be focusable views (Button...etc). If a child view isn't focusable by default ( something like a TextView) you will have to manually enable the view to be focusable via XML with android:focusable="true" or in code with View.setFocusable(true).

public class CustomTitleView extends RelativeLayout implements TitleViewAdapter.Provider {

    .... all your custom view stuff....

    @Override
    public View focusSearch(View focused, int direction) {

        View nextFoundFocusableViewInLayout = null;

        // Only concerned about focusing left and right at the moment
        if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {

            // Try to find the next focusable item in this layout for the supplied direction
            int nextFoundFocusableViewInLayoutId = -1;
            switch(direction) {
                case View.FOCUS_LEFT :
                    nextFoundFocusableViewInLayoutId = focused.getNextFocusLeftId();
                    break;
                case View.FOCUS_RIGHT :
                    nextFoundFocusableViewInLayoutId = focused.getNextFocusRightId();
                    break;
            }

            // View id for next focus direction found....get the View
            if (nextFoundFocusableViewInLayoutId != -1) {
                nextFoundFocusableViewInLayout = findViewById(nextFoundFocusableViewInLayoutId);
            }
        }

        //  Return the found View in the layout if it's focusable
        if (nextFoundFocusableViewInLayout != null && nextFoundFocusableViewInLayout.isFocusable()) {
            return nextFoundFocusableViewInLayout;
        } else {
            // No focusable view found in layout...propagate to super (should invoke the BrowseFrameLayout.OnFocusSearchListener
            return super.focusSearch(focused, direction);
        }
    }

    @Override
    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
        // Gives focus to the SearchOrb first....if not...default to normal descendant focus search
        return getSearchAffordanceView().requestFocus() || super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    }
}
dell116
  • 5,835
  • 9
  • 52
  • 70
2

In BrowseFrameLayout the normal focusSearch() is intercepted by the OnFocusSearchListener:

@Override
public View focusSearch(View focused, int direction) {
    if (mListener != null) {
        View view = mListener.onFocusSearch(focused, direction);
        if (view != null) {
            return view;
        }
    }
    return super.focusSearch(focused, direction);
}

and will most likely return null in your case, see:

private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
        new BrowseFrameLayout.OnFocusSearchListener() {
    @Override
    public View onFocusSearch(View focused, int direction) {
        // if headers is running transition,  focus stays
        if (mCanShowHeaders && isInHeadersTransition()) {
            return focused;
        }
        if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);

        if (getTitleView() != null && focused != getTitleView() &&
                direction == View.FOCUS_UP) {
            return getTitleView();
        }
        if (getTitleView() != null && getTitleView().hasFocus() &&
                direction == View.FOCUS_DOWN) {
            return mCanShowHeaders && mShowingHeaders ?
                    mHeadersFragment.getVerticalGridView() : mMainFragment.getView();
        }

        boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
        int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
        int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
        if (mCanShowHeaders && direction == towardStart) {
            if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
                return focused;
            }
            return mHeadersFragment.getVerticalGridView();
        } else if (direction == towardEnd) {
            if (isVerticalScrolling()) {
                return focused;
            } else if (mMainFragment != null && mMainFragment.getView() != null) {
                return mMainFragment.getView();
            }
            return focused;
        } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
            // disable focus_down moving into PageFragment.
            return focused;
        } else {
            return null;
        }
    }
};

A pretty ugly workaround is to extend your own BrowseFragment or BrowseFrameLayout

Teng-pao Yu
  • 1,313
  • 15
  • 30
0

If you want to have more customization over the focusable items on the screen, you may want to take a look at the nextFocusForward and similar XML attributes in the docs.

Nick Felker
  • 11,536
  • 1
  • 21
  • 35