I have used Google sample project of android tv leanback library as a reference.
So, my question is how to add items (i.e. Button, ImageView, TextView) along with search button in BrowseFragment Header in Android TV.
I can able to hide search button using commenting below code, but I can not able to add items along with search button.
setOnSearchClickedListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(getActivity(), SearchActivity.class);
startActivity(intent);
}
});
Once you have constructed a presenter class for your media items, you can build an adapter and attach it to the BrowseSupportFragment to display those items on screen for browsing by the user. The following example code demonstrates how to construct an adapter to display categories and items in those categories using the StringPresenter class...
The row headers in the browse fragment, with both an icon and a text label. The layout for the row header is defined as follows: Use a Presenter and implement the abstract methods to create, bind, and unbind the view holder. The following example shows how to bind the viewholder with two views, an ImageView and a TextView .
Finally, in the BrowseSupportFragment implementation that displays the catalog browser, use the setHeaderPresenterSelector () method to set the presenter for the row header, as shown in the following example.
In order to add visual interest to a media-browsing app on TV, you can update the background image as users browse through content. This technique can make interaction with your app more cinematic and enjoyable.
I am not sure but I think, You need to Customize Browse Fragment. You can extend Browse Fragment in a java class and then try to customize it or try to make it invisible. You can Customize Row Fragment and Header Fragment and create a custom Frame layout.
activity_main.xml
<android.support.v17.leanback.widget.SearchOrbView
android:id="@+id/custom_search_orb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="27dp"
android:layout_marginLeft="56dp"
android:layout_gravity="top|left"
android:visibility="gone"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello"/>
<FrameLayout
android:id="@+id/header_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="top|left" />
<FrameLayout
android:id="@+id/rows_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|left"
android:layout_marginLeft="300dp" />
</com.ttnd.androidtv.views.CustomFrameLayout>`
CustomRowFragment.java
import android.app.LoaderManager;
import android.content.Intent;
import android.content.Loader;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v4.app.ActivityOptionsCompat;
import android.telecom.Connection;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.Utils;
import com.example.ttnd.demoapptv.R;
import com.ttnd.androidtv.models.Movie;
import com.ttnd.androidtv.presenter.CardPresenter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class CustomRowsFragment extends RowsFragment {
private ArrayObjectAdapter rowsAdapter;
private static String mVideosUrl;
// CustomHeadersFragment, scaled by 0.9 on a 1080p screen, is 600px wide.
// This is the corresponding dip size.
private static final int HEADERS_FRAGMENT_SCALE_SIZE = 300;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
int marginOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, HEADERS_FRAGMENT_SCALE_SIZE, getResources().getDisplayMetrics());
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
params.rightMargin -= marginOffset;
v.setLayoutParams(params);
rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
CardPresenter cardPresenter = new CardPresenter();
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
listRowAdapter.add(new Movie());
HeaderItem header = new HeaderItem(0, "01234");
rowsAdapter.add(new ListRow(header, listRowAdapter));
setAdapter(rowsAdapter);
//v.setBackgroundColor(getRandomColor());
return v;
}
private void loadVideoData() {
}
private int getRandomColor() {
Random rnd = new Random();
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}
public void refresh() {
getView().setPadding(Utils.convertDpToPixel(getActivity(), -24), Utils.convertDpToPixel(getActivity(), 128), Utils.convertDpToPixel(getActivity(), 300), 0);
}
}
CustomHeaderFragment.java
package android.support.v17.leanback.app;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.FocusHighlightHelper;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowHeaderPresenter;
import android.support.v17.leanback.widget.SinglePresenterSelector;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnLayoutChangeListener;
import android.widget.FrameLayout;
public class HeadersFragment extends BaseRowFragment {
interface OnHeaderClickedListener {
void onHeaderClicked();
}
interface OnHeaderViewSelectedListener {
void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
private OnHeaderViewSelectedListener mOnHeaderViewSelectedListener;
private OnHeaderClickedListener mOnHeaderClickedListener;
private boolean mHeadersEnabled = true;
private boolean mHeadersGone = false;
private int mBackgroundColor;
private boolean mBackgroundColorSet;
private static final PresenterSelector sHeaderPresenter = new SinglePresenterSelector(
new RowHeaderPresenter(R.layout.lb_header));
public HeadersFragment() {
setPresenterSelector(sHeaderPresenter);
}
public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
mOnHeaderClickedListener = listener;
}
public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) {
mOnHeaderViewSelectedListener = listener;
}
@Override
VerticalGridView findGridViewFromRoot(View view) {
return (VerticalGridView) view.findViewById(R.id.browse_headers);
}
@Override
void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
int position, int subposition) {
if (mOnHeaderViewSelectedListener != null) {
if (viewHolder != null && position >= 0) {
Row row = (Row) getAdapter().get(position);
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
mOnHeaderViewSelectedListener.onHeaderSelected(
(RowHeaderPresenter.ViewHolder) vh.getViewHolder(), row);
} else {
mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
}
}
}
private final ItemBridgeAdapter.AdapterListener mAdapterListener =
new ItemBridgeAdapter.AdapterListener() {
@Override
public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
View headerView = viewHolder.getViewHolder().view;
headerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnHeaderClickedListener != null) {
mOnHeaderClickedListener.onHeaderClicked();
}
}
});
headerView.setFocusable(true);
headerView.setFocusableInTouchMode(true);
if (mWrapper != null) {
viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
} else {
headerView.addOnLayoutChangeListener(sLayoutChangeListener);
}
}
};
private static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
v.setPivotY(v.getMeasuredHeight() / 2);
}
};
@Override
int getLayoutResourceId() {
return R.layout.lb_headers_fragment;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final VerticalGridView listView = getVerticalGridView();
if (listView == null) {
return;
}
if (getBridgeAdapter() != null) {
FocusHighlightHelper.setupHeaderItemFocusHighlight(listView);
}
view.setBackgroundColor(getBackgroundColor());
updateFadingEdgeToBrandColor(getBackgroundColor());
updateListViewVisibility();
}
private void updateListViewVisibility() {
final VerticalGridView listView = getVerticalGridView();
if (listView != null) {
getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
if (!mHeadersGone) {
if (mHeadersEnabled) {
listView.setChildrenVisibility(View.VISIBLE);
} else {
listView.setChildrenVisibility(View.INVISIBLE);
}
}
}
}
void setHeadersEnabled(boolean enabled) {
mHeadersEnabled = enabled;
updateListViewVisibility();
}
void setHeadersGone(boolean gone) {
mHeadersGone = gone;
updateListViewVisibility();
}
static class NoOverlappingFrameLayout extends FrameLayout {
public NoOverlappingFrameLayout(Context context) {
super(context);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
}
private final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
@Override
public void wrap(View wrapper, View wrapped) {
((FrameLayout) wrapper).addView(wrapped);
}
@Override
public View createWrapper(View root) {
return new NoOverlappingFrameLayout(root.getContext());
}
};
@Override
void updateAdapter() {
super.updateAdapter();
ItemBridgeAdapter adapter = getBridgeAdapter();
if (adapter != null) {
adapter.setAdapterListener(mAdapterListener);
adapter.setWrapper(mWrapper);
}
if (adapter != null && getVerticalGridView() != null) {
FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView());
}
}
void setBackgroundColor(int color) {
mBackgroundColor = color;
mBackgroundColorSet = true;
if (getView() != null) {
getView().setBackgroundColor(mBackgroundColor);
updateFadingEdgeToBrandColor(mBackgroundColor);
}
}
private void updateFadingEdgeToBrandColor(int backgroundColor) {
View fadingView = getView().findViewById(R.id.fade_out_edge);
Drawable background = fadingView.getBackground();
if (background instanceof GradientDrawable) {
background.mutate();
((GradientDrawable) background).setColors(
new int[] {Color.TRANSPARENT, backgroundColor});
}
}
int getBackgroundColor() {
if (getActivity() == null) {
throw new IllegalStateException("Activity must be attached");
}
if (mBackgroundColorSet) {
return mBackgroundColor;
}
TypedValue outValue = new TypedValue();
if (getActivity().getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true)) {
return getResources().getColor(outValue.resourceId);
}
return getResources().getColor(R.color.lb_default_brand_color);
}
@Override
void onTransitionStart() {
super.onTransitionStart();
if (!mHeadersEnabled) {
final VerticalGridView listView = getVerticalGridView();
if (listView != null) {
listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
if (listView.hasFocus()) {
listView.requestFocus();
}
}
}
}
@Override
void onTransitionEnd() {
if (mHeadersEnabled) {
final VerticalGridView listView = getVerticalGridView();
if (listView != null) {
listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
if (listView.hasFocus()) {
listView.requestFocus();
}
}
}
super.onTransitionEnd();
}
}
CustomFrameLayout.java
package com.ttnd.androidtv.views;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
public class CustomFrameLayout extends FrameLayout {
public interface OnFocusSearchListener {
View onFocusSearch(View focused, int direction);
}
public interface OnChildFocusListener {
boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect);
void onRequestChildFocus(View child, View focused);
}
public CustomFrameLayout(Context context) {
this(context, null, 0);
}
public CustomFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private OnFocusSearchListener mListener;
private OnChildFocusListener mOnChildFocusListener;
public void setOnFocusSearchListener(OnFocusSearchListener listener) {
mListener = listener;
}
public void setOnChildFocusListener(OnChildFocusListener listener) {
mOnChildFocusListener = listener;
}
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
if (mOnChildFocusListener != null) {
return mOnChildFocusListener.onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
@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);
}
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
if (mOnChildFocusListener != null) {
mOnChildFocusListener.onRequestChildFocus(child, focused);
}
}
}
CardPresenter.java
package com.ttnd.androidtv.presenter;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.Presenter;
import android.util.Log;
import android.view.ViewGroup;
import com.bumptech.glide.Glide;
import com.example.ttnd.demoapptv.R;
import com.ttnd.androidtv.models.Movie;
public class CardPresenter extends Presenter {
private static final String TAG = "CardPresenter";
private static int CARD_WIDTH = 313;
private static int CARD_HEIGHT = 176;
private static int sSelectedBackgroundColor;
private static int sDefaultBackgroundColor;
private Drawable mDefaultCardImage;
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
Log.d(TAG, "onCreateViewHolder");
sDefaultBackgroundColor = parent.getResources().getColor(R.color.default_background);
sSelectedBackgroundColor = parent.getResources().getColor(R.color.selected_background);
mDefaultCardImage = parent.getResources().getDrawable(R.drawable.ic_launcher);
ImageCardView cardView = new ImageCardView(parent.getContext()) {
@Override
public void setSelected(boolean selected) {
updateCardBackgroundColor(this, selected);
super.setSelected(selected);
}
};
cardView.setFocusable(true);
cardView.setFocusableInTouchMode(true);
updateCardBackgroundColor(cardView, false);
return new ViewHolder(cardView);
}
private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
view.setBackgroundColor(color);
view.findViewById(R.id.info_field).setBackgroundColor(color);
}
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
Movie movie = (Movie) item;
ImageCardView cardView = (ImageCardView) viewHolder.view;
Log.d(TAG, "onBindViewHolder");
if (movie.getCardImageUrl() != null) {
cardView.setTitleText(movie.getTitle());
cardView.setContentText(movie.getStudio());
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
Glide.with(viewHolder.view.getContext())
.load(movie.getCardImageUrl())
.centerCrop()
.error(mDefaultCardImage)
.into(cardView.getMainImageView());
}
}
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
Log.d(TAG, "onUnbindViewHolder");
ImageCardView cardView = (ImageCardView) viewHolder.view;
cardView.setBadgeImage(null);
cardView.setMainImage(null);
}
}
Movie.java
package com.ttnd.androidtv.models;
import android.os.Parcel;
import android.os.Parcelable;
import java.net.URI;
import java.net.URISyntaxException;
public class Movie implements Parcelable {
private static final String TAG = "Movie";
static final long serialVersionUID = 727566175075960653L;
private static int sCount = 0;
private String mId = "0";
private String mTitle = "Title Here";
private String mDescription = "Description Here";
private String mBgImageUrl = "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Demo%20Slam/Google%20Demo%20Slam_%2020ft%20Search/bg.jpg";
private String mCardImageUrl = "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Zeitgeist/Zeitgeist%202010_%20Year%20in%20Review/card.jpg";
private String mVideoUrl = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
private String mStudio = "Studio Here";
private String mCategory = "Category Here";
public Movie() {
}
public Movie(Parcel in){
String[] data = new String[8];
in.readStringArray(data);
mId = data[0];
mTitle = data[1];
mDescription = data[2];
mBgImageUrl = data[3];
mCardImageUrl = data[4];
mVideoUrl = data[5];
mStudio = data[6];
mCategory = data[7];
}
public static String getCount() {
return Integer.toString(sCount);
}
public static void incrementCount() {
sCount++;
}
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getTitle() {
return mTitle;
}
public void setTitle(String title) {
mTitle = title;
}
public String getDescription() {
return mDescription;
}
public void setDescription(String description) {
mDescription = description;
}
public String getStudio() {
return mStudio;
}
public void setStudio(String studio) {
mStudio = studio;
}
public String getVideoUrl() {
return mVideoUrl;
}
public void setVideoUrl(String videoUrl) {
mVideoUrl = videoUrl;
}
public String getBackgroundImageUrl() {
return mBgImageUrl;
}
public void setBackgroundImageUrl(String bgImageUrl) {
mBgImageUrl = bgImageUrl;
}
public String getCardImageUrl() {
return mCardImageUrl;
}
public void setCardImageUrl(String cardImageUrl) {
mCardImageUrl = cardImageUrl;
}
public String getCategory() {
return mCategory;
}
public void setCategory(String category) {
mCategory = category;
}
public URI getBackgroundImageURI() {
try {
return new URI(getBackgroundImageUrl());
} catch (URISyntaxException e) {
return null;
}
}
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(new String[] {mId,
mTitle,
mDescription,
mBgImageUrl,
mCardImageUrl,
mVideoUrl,
mStudio,
mCategory});
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(200);
sb.append("Movie{");
sb.append("mId=" + mId);
sb.append(", mTitle='" + mTitle + '\'');
sb.append(", mVideoUrl='" + mVideoUrl + '\'');
sb.append(", backgroundImageUrl='" + mBgImageUrl + '\'');
sb.append(", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'');
sb.append(", mCardImageUrl='" + mCardImageUrl + '\'');
sb.append('}');
return sb.toString();
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public Movie createFromParcel(Parcel in) {
return new Movie(in);
}
public Movie[] newArray(int size) {
return new Movie[size];
}
};
}
I found a blog while I was googling related to this type of customization. You can take a look of URL below: https://medium.com/building-for-android-tv
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With