The 2D Picker pattern in Android Wear allows users to navigate and choose from a set of items shown as pages. The Wearable UI Library lets you easily implement this pattern using a page grid, which is a layout manager that allows users to scroll vertically and horizontally through pages of data.
To implement this pattern, you add a GridViewPager
element to the layout of your activity and implement an adapter that provides a set of pages by extending theFragmentGridPagerAdapter
class.
Add a GridViewPager
element to your layout definition as follows:
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
A page adapter provides a set of pages to populate a GridViewPager
component. To implement this adapter, you extend the FragmentGridPagerAdapter
class from the Wearable UI Library
The following snippet shows how to provide a set of static cards with custom background images:
public class SampleGridPagerAdapter extends FragmentGridPagerAdapter { private final Context mContext; private List mRows; public SampleGridPagerAdapter(Context ctx, FragmentManager fm) { super(fm); mContext = ctx; } static final int[] BG_IMAGES = new int[] { R.drawable.debug_background_1, ... R.drawable.debug_background_5 }; // A simple container for static data in each page private static class Page { // static resources int titleRes; int textRes; int iconRes; ... } // Create a static set of pages in a 2D array private final Page[][] PAGES = { ... }; // Override methods in FragmentGridPagerAdapter ... }
The adapter calls getFragment()
and getBackgroundForRow()
to retrieve the content to display for each row:
// Obtain the UI fragment at the specified position @Override public Fragment getFragment(int row, int col) { Page page = PAGES[row][col]; String title = page.titleRes != 0 ? mContext.getString(page.titleRes) : null; String text = page.textRes != 0 ? mContext.getString(page.textRes) : null; CardFragment fragment = CardFragment.create(title, text, page.iconRes); // Advanced settings (card gravity, card expansion/scrolling) fragment.setCardGravity(page.cardGravity); fragment.setExpansionEnabled(page.expansionEnabled); fragment.setExpansionDirection(page.expansionDirection); fragment.setExpansionFactor(page.expansionFactor); return fragment; } // Obtain the background image for the row @Override public Drawable getBackgroundForRow(int row) { return mContext.getResources().getDrawable( (BG_IMAGES[row % BG_IMAGES.length]), null); }
The following example shows how to retrieve the background to display for a specific page in the grid:
// Obtain the background image for the specific page @Override public Drawable getBackgroundForPage(int row, int column) { if( row == 2 && column == 1) { // Place image at specified position return mContext.getResources().getDrawable(R.drawable.bugdroid_large, null); } else { // Default to background image for row return GridPagerAdapter.BACKGROUND_NONE; } }
The getRowCount()
method tells the adapter how many rows of content are available, and thegetColumnCount()
method tells the adapter how many columns of content are available for each of the rows.
// Obtain the number of pages (vertical) @Override public int getRowCount() { return PAGES.length; } // Obtain the number of pages (horizontal) @Override public int getColumnCount(int rowNum) { return PAGES[rowNum].length; }You can also use a
GridViewPager
component to implement a 1D picker with only one row or only one column.
In your activity, assign an instance of your adapter implementation to the GridViewPager
component:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... final GridViewPager pager = (GridViewPager) findViewById(R.id.pager); pager.setAdapter(new SampleGridPagerAdapter(this, getFragmentManager())); } }> Showing Confirmations
Confirmations in Android Wear apps use the whole screen or a larger portion of it than those in handheld apps. This ensures that users can see these confirmations by just glancing at the screen and that they have large enough touch targets to cancel an action.
Automatic confirmation timers let users cancel an action they just performed. When the user performs the action, your app shows a button to cancel the action with a timer animation and starts the timer. The user has the option to cancel the action until the timer finishes. Your app gets notified if the user cancels the action and when the timer expires.
To show a confirmation timer when users complete an action in your app:
element to your layout.DelayedConfirmationListener
interface in your activity. Add the
element to your layout as follows:
android:id="@+id/delayed_confirm"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/cancel_circle"
app:circle_border_color="@color/lightblue"
app:circle_border_width="4dp"
app:circle_radius="16dp">
To be notified when the timer finishes or when users tap on it, implement the corresponding listener methods in your activity:
public class WearActivity extends Activity implements DelayedConfirmationView.DelayedConfirmationListener { private DelayedConfirmationView mDelayedView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_wear_activity); mDelayedView = (DelayedConfirmationView) findViewById(R.id.delayed_confirm); mDelayedView.setListener(this); } @Override public void onTimerFinished(View view) { // User didn't cancel, perform the action } @Override public void onTimerSelected(View view) { // User canceled, abort the action } }
To start the timer, add the following code to the point in your activity where users select an action:
// Two seconds to cancel the action mDelayedView.setTotalTimeMs(2000); // Start the timer mDelayedView.start();
Then determine the result of the user action and start the activity with an intent:
Intent intent = new Intent(this, ConfirmationActivity.class); intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, ConfirmationActivity.SUCCESS_ANIMATION); intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE, getString(R.string.msg_sent)); startActivity(intent);
After showing the confirmation animation, ConfirmationActivity
finishes and your activity resumes.
By default, users exit Android Wear activities by swiping from left to right. If the app contains horizontally scrollable content, users first have to navigate to the edge of the content and then swipe again from left to right to exit the app.
For more immersive experiences, like an app that can scroll a map in any direction, you can disable the swipe to exit gesture in your app. However, if you disable it, you must implement the long-press-to-dismiss UI pattern to let users exit your app using the DismissOverlayView
class from the Wearable UI Library. You must also inform your users the first time they run your app that they can exit using a long press.
To use the DismissOverlayView
class in your activity, add this element to your layout definition such that it covers the whole screen and is placed above all other views.
The following example shows how to add the
element:
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
android:id="@+id/dismiss_overlay"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
In your activity, obtain the
element and set some introductory text. This text is shown to users the first time they run your app to inform them that they can exit the app using a long press gesture. Then use a GestureDetector
to detect a long press:
public class WearActivity extends Activity { private DismissOverlayView mDismissOverlay; private GestureDetector mDetector; public void onCreate(Bundle savedState) { super.onCreate(savedState); setContentView(R.layout.wear_activity); // Obtain the DismissOverlayView element mDismissOverlay = (DismissOverlayView) findViewById(R.id.dismiss_overlay); mDismissOverlay.setIntroText(R.string.long_press_intro); mDismissOverlay.showIntroIfNecessary(); // Configure a gesture detector mDetector = new GestureDetector(this, new SimpleOnGestureListener() { public void onLongPress(MotionEvent ev) { mDismissOverlay.show(); } }); } // Capture long presses @Override public boolean onTouchEvent(MotionEvent ev) { return mDetector.onTouchEvent(ev) || super.onTouchEvent(ev); } }
When the system detects a long press gesture, the
element shows an Exit button, which terminates your activity if the user presses it.