diff options
author | Roberto Perez <robertoalexis@google.com> | 2018-04-25 20:37:46 -0700 |
---|---|---|
committer | Roberto Perez <robertoalexis@google.com> | 2018-04-27 14:57:34 -0700 |
commit | 5a5a06b91563988c671319c68d9ce1a66f352a95 (patch) | |
tree | c93d4aadf304fc0fd6c330cd367f911957cedb29 /src/com/android/car | |
parent | a72deaa7b41c4f2677aac8092823176e61306b93 (diff) | |
download | Media-5a5a06b91563988c671319c68d9ce1a66f352a95.tar.gz |
Updating tabs to match design. Add the new app-switch icon.
Implement support for new navigation states.
Bug: 78571420
Test: Tested on Big Dog
Change-Id: Ic5dd3c5d6711d8ee7a088dd64c5dd62aed0024d8
Diffstat (limited to 'src/com/android/car')
-rw-r--r-- | src/com/android/car/media/MediaActivity.java | 74 | ||||
-rw-r--r-- | src/com/android/car/media/widgets/AppBarView.java | 269 | ||||
-rw-r--r-- | src/com/android/car/media/widgets/MediaItemTabView.java | 26 |
3 files changed, 327 insertions, 42 deletions
diff --git a/src/com/android/car/media/MediaActivity.java b/src/com/android/car/media/MediaActivity.java index 3ac5396..3a7fa12 100644 --- a/src/com/android/car/media/MediaActivity.java +++ b/src/com/android/car/media/MediaActivity.java @@ -19,7 +19,6 @@ import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; -import android.support.design.widget.TabLayout; import android.support.design.widget.AppBarLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; @@ -34,7 +33,7 @@ import com.android.car.media.common.MediaSource; import com.android.car.media.common.PlaybackControls; import com.android.car.media.common.PlaybackModel; import com.android.car.media.drawer.MediaDrawerController; -import com.android.car.media.widgets.MediaItemTabView; +import com.android.car.media.widgets.AppBarView; import com.android.car.media.widgets.MetadataView; import java.util.ArrayList; @@ -68,16 +67,15 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C private PlaybackModel mPlaybackModel; /** Layout views */ - private TabLayout mTabLayout; + private AppBarView mAppBarView; private CrossfadeImageView mAlbumBackground; private PlaybackFragment mPlaybackFragment; - private AppBarLayout mAppBarLayout; + private AppBarLayout mDrawerBarLayout; private View mBrowseScrim; private PlaybackControls mPlaybackControls; private MetadataView mMetadataView; private ViewGroup mBrowseControlsContainer; - /** Current state */ private MediaItemMetadata mCurrentMetadata; private Fragment mCurrentFragment; @@ -109,27 +107,30 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C updateMetadata(); } }; - private TabLayout.OnTabSelectedListener mTabSelectedListener = - new TabLayout.OnTabSelectedListener() { + private AppBarView.AppBarListener mAppBarListener = new AppBarView.AppBarListener() { @Override - public void onTabSelected(TabLayout.Tab tab) { - Log.i(TAG, "onTabSelected: " + tab.getTag()); + public void onTabSelected(MediaItemMetadata item) { mMode = Mode.BROWSING; - updateBrowseFragment((MediaItemMetadata) tab.getTag()); + updateBrowseFragment(item); updateMetadata(); } @Override - public void onTabUnselected(TabLayout.Tab tab) { - // Nothing to do + public void onBack() { + if (mCurrentFragment != null && mCurrentFragment instanceof BrowseFragment) { + BrowseFragment fragment = (BrowseFragment) mCurrentFragment; + fragment.navigateBack(); + } } @Override - public void onTabReselected(TabLayout.Tab tab) { - Log.i(TAG, "onTabReselected: " + tab.getTag()); - mMode = Mode.BROWSING; - updateBrowseFragment((MediaItemMetadata) tab.getTag()); - updateMetadata(); + public void onCollapse() { + switchToMode(Mode.BROWSING); + } + + @Override + public void onAppSelection() { + // TODO(b/78602199): Implement app selection logic } }; @@ -152,12 +153,15 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C .getBoolean(R.bool.force_browse_tabs); mDrawerController = new MediaDrawerController(this, getDrawerController()); getDrawerController().setRootAdapter(getRootAdapter()); - mTabLayout = findViewById(R.id.tabs); - mTabLayout.addOnTabSelectedListener(mTabSelectedListener); + mAppBarView = findViewById(R.id.app_bar); + mAppBarView.setListener(mAppBarListener); + // TODO(b/78602199): Implement actual app selection logic + mAppBarView.setAppSelection(true); mPlaybackFragment = new PlaybackFragment(); mPlaybackModel = new PlaybackModel(this); mMaxBrowserTabs = getResources().getInteger(R.integer.max_browse_tabs); - mAppBarLayout = findViewById(androidx.car.R.id.appbar); + mDrawerBarLayout = findViewById(androidx.car.R.id.appbar); + mDrawerBarLayout.setVisibility(View.GONE); mAlbumBackground = findViewById(R.id.media_background); mBrowseScrim = findViewById(R.id.browse_scrim); mPlaybackControls = findViewById(R.id.controls); @@ -266,6 +270,12 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C mMediaSource.getPackageName()); mMediaSource.subscribe(mMediaSourceObserver); } + // TODO: Show rounded icon + mAppBarView.setAppIcon(null); + mAppBarView.setTitle(mMediaSource.getName()); + } else { + mAppBarView.setAppIcon(null); + mAppBarView.setTitle(null); } } @@ -303,33 +313,19 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C .filter(item -> item.isPlayable()) .collect(Collectors.toList()); - Log.i(TAG, "Updating top level: " + browsableTopLevel.size() + " browsable items, " - + playableTopLevel.size() + " playable items"); - mTabLayout.removeAllTabs(); // Show tabs if: // - We have some browesable items and we are forced to show them as tabs, // - or we have only browsable items on the top level and they are not too many. if ((!browsableTopLevel.isEmpty() && mForceBrowseTabs) || (playableTopLevel.isEmpty() && !browsableTopLevel.isEmpty() && browsableTopLevel.size() <= mMaxBrowserTabs)) { - mAppBarLayout.setVisibility(View.GONE); - mTabLayout.setVisibility(View.VISIBLE); - // Temporarily removing the listener, to prevent initial tab selection event. - mTabLayout.removeOnTabSelectedListener(mTabSelectedListener); - int count = 0; - for (MediaItemMetadata item : browsableTopLevel) { - MediaItemTabView tab = new MediaItemTabView(this, item); - mTabLayout.addTab(mTabLayout.newTab().setCustomView(tab).setTag(item)); - count++; - if (count >= mMaxBrowserTabs) { - break; - } - } - mTabLayout.addOnTabSelectedListener(mTabSelectedListener); + mDrawerBarLayout.setVisibility(View.GONE); + mAppBarView.setVisibility(View.VISIBLE); + mAppBarView.setItems(browsableTopLevel); updateBrowseFragment(browsableTopLevel.get(0)); } else { - mAppBarLayout.setVisibility(View.VISIBLE); - mTabLayout.setVisibility(View.INVISIBLE); + mDrawerBarLayout.setVisibility(View.VISIBLE); + mAppBarView.setVisibility(View.INVISIBLE); updateBrowseFragment(null); } } diff --git a/src/com/android/car/media/widgets/AppBarView.java b/src/com/android/car/media/widgets/AppBarView.java new file mode 100644 index 0000000..622e626 --- /dev/null +++ b/src/com/android/car/media/widgets/AppBarView.java @@ -0,0 +1,269 @@ +package com.android.car.media.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.design.widget.TabLayout; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.android.car.media.R; +import com.android.car.media.common.MediaItemMetadata; +import com.android.car.media.common.MediaSource; + +import java.util.ArrayList; +import java.util.List; + +/** + * Media template application bar. A detailed explanation of all possible states of this + * application bar can be seen at {@link AppBarView.State}. + */ +public class AppBarView extends RelativeLayout { + /** Default number of tabs to show on this app bar */ + private static int DEFAULT_MAX_TABS = 4; + + private List<MediaSource> mMediaSources = new ArrayList<>(); + private LinearLayout mTabsContainer; + private ImageView mAppIcon; + private ImageView mAppSwitchIcon; + private ImageView mNavIcon; + private TextView mTitle; + private ViewGroup mAppSwitchContainer; + private Context mContext; + private int mMaxTabs; + private Drawable mArrowDropDown; + private Drawable mArrowDropUp; + private Drawable mArrowBack; + private Drawable mCollapse; + private State mState = State.IDLE; + private AppBarListener mListener; + + /** + * Application bar listener + */ + public interface AppBarListener { + /** + * Invoked when the user selects an item from the tabs + */ + void onTabSelected(MediaItemMetadata item); + + /** + * Invoked when the user clicks on the back button + */ + void onBack(); + + /** + * Invoked when the user clicks on the collapse button + */ + void onCollapse(); + + /** + * Invoked when the user clicks on the app selection switch + */ + void onAppSelection(); + } + + /** + * Possible states of this application bar + */ + public enum State { + /** + * Normal application state. If we are able to obtain media items from the media + * source application, we display them as tabs. Otherwise we show the application name. + */ + IDLE, + /** + * Indicates that the user has navigated into an element. In this case we show + * the name of the element and we disable the back button. + */ + STACKED, + /** + * Indicates that we have expanded a view that can be collapsed. We show the + * title of the application and a collapse icon + */ + EXPANDED, + /** + * Used to indicate that the user is inside the app selector. In this case we disable + * navigation, we show the title of the application and we show the app switch icon + * point up + */ + APP_SELECTION + } + + public AppBarView(Context context) { + this(context, null); + } + + public AppBarView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AppBarView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AppBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context, attrs, defStyleAttr, defStyleRes); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + TypedArray ta = context.obtainStyledAttributes( + attrs, R.styleable.AppBarView, defStyleAttr, defStyleRes); + mMaxTabs = ta.getInteger(R.styleable.AppBarView_max_tabs, DEFAULT_MAX_TABS); + ta.recycle(); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.appbar_view, this, true); + + mContext = context; + mTabsContainer = findViewById(R.id.tabs); + mNavIcon = findViewById(R.id.nav_icon); + mNavIcon.setOnClickListener(view -> onNavIconClicked()); + mAppIcon = findViewById(R.id.app_icon); + mAppSwitchIcon = findViewById(R.id.app_switch_icon); + mAppSwitchContainer = findViewById(R.id.app_switch_container); + mAppSwitchContainer.setOnClickListener(view -> onAppSwitchClicked()); + mTitle = findViewById(R.id.title); + mArrowDropDown = getResources().getDrawable(R.drawable.ic_arrow_drop_down, null); + mArrowDropUp = getResources().getDrawable(R.drawable.ic_arrow_drop_up, null); + mArrowBack = getResources().getDrawable(R.drawable.ic_arrow_back, null); + mCollapse = getResources().getDrawable(R.drawable.ic_expand_more, null); + + setState(State.IDLE); + } + + private void onNavIconClicked() { + if (mListener == null) { + return; + } + switch (mState) { + case STACKED: + mListener.onBack(); + break; + case EXPANDED: + mListener.onCollapse(); + break; + } + } + + private void onAppSwitchClicked() { + if (mListener == null) { + return; + } + mListener.onAppSelection(); + } + + /** + * Sets a listener of this application bar events. In order to avoid memory leaks, consumers + * must reset this reference by setting the listener to null. + */ + public void setListener(AppBarListener listener) { + mListener = listener; + } + + /** + * Updates the list of items to show in the application bar tabs. + */ + public void setItems(List<MediaItemMetadata> items) { + mTabsContainer.removeAllViews(); + + if (items != null) { + int count = 0; + int padding = mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_4); + int tabWidth = mContext.getResources().getDimensionPixelSize(R.dimen.browse_tab_width) + + 2 * padding; + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( + tabWidth, ViewGroup.LayoutParams.MATCH_PARENT); + for (MediaItemMetadata item : items) { + MediaItemTabView tab = new MediaItemTabView(mContext, item); + mTabsContainer.addView(tab); + tab.setLayoutParams(layoutParams); + tab.setOnClickListener(view -> { + if (mListener != null) { + mListener.onTabSelected(item); + } + }); + tab.setPadding(padding, 0, padding, 0); + tab.requestLayout(); + count++; + if (count >= mMaxTabs) { + break; + } + } + } + + if (mState == State.IDLE) { + boolean hasItems = items != null && !items.isEmpty(); + mTabsContainer.setVisibility(hasItems ? View.VISIBLE : View.GONE); + mTitle.setVisibility(hasItems ? View.GONE : View.VISIBLE); + } + } + + /** + * Updates the title to display when the bar is not showing tabs. + */ + public void setTitle(CharSequence title) { + mTitle.setText(title); + } + + /** + * Updates the application icon to show next to the application switcher. + */ + public void setAppIcon(Bitmap icon) { + mAppIcon.setImageBitmap(icon); + mAppIcon.setVisibility(icon != null ? View.VISIBLE : View.GONE); + } + + /** + * Indicates whether or not the application switcher should be enabled. + */ + public void setAppSelection(boolean enabled) { + mAppSwitchIcon.setVisibility(enabled ? View.VISIBLE : View.GONE); + } + + /** + * Updates the state of the bar. + */ + public void setState(State state) { + boolean hasItems = mTabsContainer.getChildCount() > 0; + mState = state; + + switch (state) { + case IDLE: + mNavIcon.setVisibility(View.GONE); + mTabsContainer.setVisibility(hasItems ? View.VISIBLE : View.GONE); + mTitle.setVisibility(hasItems ? View.GONE : View.VISIBLE); + mAppSwitchIcon.setImageDrawable(mArrowDropDown); + break; + case STACKED: + mNavIcon.setImageDrawable(mArrowBack); + mNavIcon.setVisibility(View.VISIBLE); + mTabsContainer.setVisibility(View.GONE); + mTitle.setVisibility(View.VISIBLE); + mAppSwitchIcon.setImageDrawable(mArrowDropDown); + break; + case EXPANDED: + mNavIcon.setImageDrawable(mCollapse); + mNavIcon.setVisibility(View.VISIBLE); + mTabsContainer.setVisibility(View.GONE); + mTitle.setVisibility(View.VISIBLE); + mAppSwitchIcon.setImageDrawable(mArrowDropDown); + break; + case APP_SELECTION: + mNavIcon.setVisibility(View.GONE); + mTabsContainer.setVisibility(View.GONE); + mTitle.setVisibility(View.VISIBLE); + mAppSwitchIcon.setImageDrawable(mArrowDropUp); + break; + } + } +} diff --git a/src/com/android/car/media/widgets/MediaItemTabView.java b/src/com/android/car/media/widgets/MediaItemTabView.java index c70cdff..9701926 100644 --- a/src/com/android/car/media/widgets/MediaItemTabView.java +++ b/src/com/android/car/media/widgets/MediaItemTabView.java @@ -16,7 +16,10 @@ package com.android.car.media.widgets; +import android.annotation.NonNull; import android.content.Context; +import android.content.res.TypedArray; +import android.view.Gravity; import android.view.LayoutInflater; import android.widget.ImageView; import android.widget.LinearLayout; @@ -29,22 +32,39 @@ import com.android.car.media.common.MediaItemMetadata; * A view representing a media item to be included in the tab bar at the top of the UI. */ public class MediaItemTabView extends LinearLayout { - private TextView mTitleView; - private ImageView mImageView; + private final TextView mTitleView; + private final ImageView mImageView; + private final MediaItemMetadata mItem; /** * Creates a new tab for the given media item. */ - public MediaItemTabView(Context context, MediaItemMetadata item) { + public MediaItemTabView(@NonNull Context context, @NonNull MediaItemMetadata item) { super(context); LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.tab_view, this, true); setOrientation(LinearLayout.VERTICAL); + setFocusable(true); + setGravity(Gravity.CENTER); + int[] attrs = new int[]{android.R.attr.selectableItemBackground}; + TypedArray typedArray = context.obtainStyledAttributes(attrs); + int backgroundResource = typedArray.getResourceId(0, 0); + setBackgroundResource(backgroundResource); + + mItem = item; mImageView = findViewById(R.id.icon); MediaItemMetadata.updateImageView(context, item, mImageView, 0); mTitleView = findViewById(R.id.title); mTitleView.setText(item.getTitle()); } + + /** + * Returns the item represented by this view + */ + @NonNull + public MediaItemMetadata getItem() { + return mItem; + } } |