diff --git a/app/build.gradle b/app/build.gradle index 98fb09f..139271b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,9 +51,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.config } - debug { - applicationIdSuffix ".debug" - } } productFlavors { } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3db3c62..6f24fec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,6 @@ diff --git a/app/src/main/java/com/philliphsu/clock2/BaseActivity.java b/app/src/main/java/com/philliphsu/clock2/BaseActivity.java index 45271c1..f898541 100644 --- a/app/src/main/java/com/philliphsu/clock2/BaseActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/BaseActivity.java @@ -59,6 +59,7 @@ public abstract class BaseActivity extends AppCompatActivity { if (ab != null) { ab.setDisplayHomeAsUpEnabled(isDisplayHomeUpEnabled()); ab.setDisplayShowTitleEnabled(isDisplayShowTitleEnabled()); + ab.setElevation(getResources().getDimension(R.dimen.appbar_elevation)); } } } diff --git a/app/src/main/java/com/philliphsu/clock2/MainActivity.java b/app/src/main/java/com/philliphsu/clock2/MainActivity.java index 0a89047..5700668 100644 --- a/app/src/main/java/com/philliphsu/clock2/MainActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/MainActivity.java @@ -1,6 +1,7 @@ package com.philliphsu.clock2; import android.content.Intent; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; @@ -8,6 +9,7 @@ import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.content.ContextCompat; import android.support.v4.view.ViewPager; import android.util.Log; import android.util.SparseArray; @@ -26,18 +28,13 @@ import butterknife.Bind; public class MainActivity extends BaseActivity { private static final String TAG = "MainActivity"; - public static final int PAGE_ALARMS = 0; - public static final int PAGE_TIMERS = 1; - public static final int PAGE_STOPWATCH = 2; - public static final String EXTRA_SHOW_PAGE = "com.philliphsu.clock2.extra.SHOW_PAGE"; - public static final int REQUEST_THEME_CHANGE = 5; + public static final int PAGE_ALARMS = 0; + public static final int PAGE_TIMERS = 1; + public static final int PAGE_STOPWATCH = 2; + public static final String EXTRA_SHOW_PAGE = "com.philliphsu.clock2.extra.SHOW_PAGE"; - @Bind(R.id.container) - ViewPager mViewPager; - @Bind(R.id.fab) - FloatingActionButton mFab; + public static final int REQUEST_THEME_CHANGE = 5; - private ModifyOffsetOnPageChangeListener mModifyFabPositionListener; /** * The {@link android.support.v4.view.PagerAdapter} that will provide * fragments for each of the sections. We use a @@ -46,7 +43,27 @@ public class MainActivity extends BaseActivity { * may be best to switch to a * {@link android.support.v4.app.FragmentStatePagerAdapter}. */ - private SectionsPagerAdapter mSectionsPagerAdapter; + private SectionsPagerAdapter mSectionsPagerAdapter; + private Drawable mAddItemDrawable; + +// // For delaying fab.show() on SCROLL_STATE_SETTLING +// private final Handler mHandler = new Handler(); +// +// private boolean mScrollStateDragging; +// private int mPageDragging = -1; // TOneverDO: initial value >= 0 +// private boolean mDraggingPastEndBoundaries; + + @Bind(R.id.container) + ViewPager mViewPager; + + @Bind(R.id.fab) + FloatingActionButton mFab; + +// // https://medium.com/@chrisbanes/appcompat-v23-2-age-of-the-vectors-91cbafa87c88#.141274xy8 +// // This is needed to load vector drawables from 23.4.0 +// static { +// AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); +// } @Override protected void onCreate(final Bundle savedInstanceState) { @@ -64,7 +81,7 @@ public class MainActivity extends BaseActivity { public void run() { if (mViewPager.getCurrentItem() == mSectionsPagerAdapter.getCount() - 1) { // Restore the FAB's translationX from a previous configuration. - mFab.setTranslationX(mViewPager.getWidth() / -2f + getFabPixelOffsetForXTranslation(mFab)); + mFab.setTranslationX(mViewPager.getWidth() / -2f + getFabPixelOffsetForXTranslation()); } } }); @@ -73,7 +90,124 @@ public class MainActivity extends BaseActivity { // primary sections of the activity. mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); mViewPager.setAdapter(mSectionsPagerAdapter); - this.mModifyFabPositionListener = new ModifyOffsetOnPageChangeListener(mFab, mSectionsPagerAdapter, mViewPager); + mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + /** + * @param position Either the current page position if the offset is increasing, + * or the previous page position if it is decreasing. + * @param positionOffset If increasing from [0, 1), scrolling right and position = currentPagePosition + * If decreasing from (1, 0], scrolling left and position = (currentPagePosition - 1) + */ + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + Log.d(TAG, String.format("pos = %d, posOffset = %f, posOffsetPixels = %d", + position, positionOffset, positionOffsetPixels)); + int pageBeforeLast = mSectionsPagerAdapter.getCount() - 2; + if (position <= pageBeforeLast) { + if (position < pageBeforeLast) { + // When the scrolling is due to tab selection between multiple tabs apart, + // this callback is called for each intermediate page, but each of those pages + // will briefly register a sparsely decreasing range of positionOffsets, always + // from (1, 0). As such, you would notice the FAB to jump back and forth between + // x-positions as each intermediate page is scrolled through. + // This is a visual optimization that ends the translation motion, immediately + // returning the FAB to its target position. + // TODO: The animation visibly skips to the end. We could interpolate + // intermediate x-positions if we cared to smooth it out. + mFab.setTranslationX(0); + } else { + // Initially, the FAB's translationX property is zero because, at its original + // position, it is not translated. setTranslationX() is relative to the view's + // left position, at its original position; this left position is taken to be + // the zero point of the coordinate system relative to this view. As your + // translationX value is increasingly negative, the view is translated left. + // But as translationX is decreasingly negative and down to zero, the view + // is translated right, back to its original position. + float translationX = positionOffsetPixels / -2f; + // NOTE: You MUST scale your own additional pixel offsets by positionOffset, + // or else the FAB will immediately translate by that many pixels, appearing + // to skip/jump. + translationX += positionOffset * getFabPixelOffsetForXTranslation(); + mFab.setTranslationX(translationX); + } + } + } + + @Override + public void onPageSelected(int position) { + Log.d(TAG, "onPageSelected"); + if (position < mSectionsPagerAdapter.getCount() - 1) { + mFab.setImageDrawable(mAddItemDrawable); + } + Fragment f = mSectionsPagerAdapter.getFragment(mViewPager.getCurrentItem()); + // NOTE: This callback is fired after a rotation, right after onStart(). + // Unfortunately, the FragmentManager handling the rotation has yet to + // tell our adapter to re-instantiate the Fragments, so our collection + // of fragments is empty. You MUST keep this check so we don't cause a NPE. + if (f instanceof BaseFragment) { + ((BaseFragment) f).onPageSelected(); + } + } +// @Override +// public void onPageScrollStateChanged(int state) { +// // TODO: This was not sufficient to prevent the user from quickly +// // hitting the fab for the previous page. +// switch (state) { +// case ViewPager.SCROLL_STATE_DRAGGING: +// if (mDraggingPastEndBoundaries) { +// return; +// } +// mScrollStateDragging = true; +// mPageDragging = mViewPager.getCurrentItem(); +// mFab.hide(); +// break; +// case ViewPager.SCROLL_STATE_SETTLING: +// if (!mScrollStateDragging) { +// mFab.hide(); +// } +// mScrollStateDragging = false; +// // getCurrentItem() has changed to the target page we're settling on. +// // 200ms is the same as show/hide animation duration +// int targetPage = mViewPager.getCurrentItem(); +// if (targetPage != 2) { // TODO: Use page constant +// int delay = mPageDragging == targetPage ? 0 : 200; +// mHandler.postDelayed(new Runnable() { +// @Override +// public void run() { +// mFab.show(); +// } +// }, delay); +// } +// mPageDragging = -1; +// break; +// case ViewPager.SCROLL_STATE_IDLE: +// // Nothing +// break; +// } +// } + }); +// mViewPager.setPageTransformer(false, new ViewPager.PageTransformer() { +// @Override +// public void transformPage(View page, float position) { +// Log.d(TAG, "position: " + position); +// // position represents a page's offset from the front-and-center position of 0 (the page +// // that is in full view). Consider pages A, B, C, D. +// // If we are now on page A (position 0), then pages B, C, and D are respectively +// // in positions 1, 2, 3. +// // If we move to the right to page B (now in position 0), then pages A, C, D are +// // respectively in positions -1, 1, 2. +// int currentPage = mViewPager.getCurrentItem(); +// // TODO: Use page constants +// // Page 0 can't move one full page position to the right (i.e. there is no page to +// // the left of page 0 that can adopt the front-and-center position of 0 while page 0 +// // moves to adopt position 1) +// mDraggingPastEndBoundaries = currentPage == 0 && position >= 0f +// // The last page can't move one full page position to the left (i.e. there +// // is no page to the right of the last page that can adopt the front-and-center +// // position of 0 while the last page moves to adopt position -1) +// || currentPage == mSectionsPagerAdapter.getCount() - 1 && position <= 0f; +// Log.d(TAG, "Draggin past end bounds: " + mDraggingPastEndBoundaries); +// } +// }); TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); tabLayout.setupWithViewPager(mViewPager); @@ -93,6 +227,8 @@ public class MainActivity extends BaseActivity { } }); + mAddItemDrawable = ContextCompat.getDrawable(this, R.drawable.ic_add_24dp); + final int initialPage = getIntent().getIntExtra(EXTRA_SHOW_PAGE, -1); if (initialPage >= 0 && initialPage <= mSectionsPagerAdapter.getCount() - 1) { // This is so we don't keep it around when the configuration changes. @@ -148,30 +284,15 @@ public class MainActivity extends BaseActivity { // http://stackoverflow.com/a/24303360/5055032 super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_THEME_CHANGE - && data != null && data.getBooleanExtra(SettingsActivity.EXTRA_THEME_CHANGED, false)) { - recreate(); + switch (requestCode) { + case REQUEST_THEME_CHANGE: + if (data != null && data.getBooleanExtra(SettingsActivity.EXTRA_THEME_CHANGED, false)) { + recreate(); + } + break; } } - @Override - protected void onStart() { - super.onStart(); - mViewPager.addOnPageChangeListener(mModifyFabPositionListener); - } - - @Override - protected void onStop() { - super.onStop(); - mViewPager.removeOnPageChangeListener(mModifyFabPositionListener); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - this.mModifyFabPositionListener = null; - } - @Override protected int layoutResId() { return R.layout.activity_main; @@ -187,11 +308,6 @@ public class MainActivity extends BaseActivity { return false; } - @Override - protected boolean isDisplayShowTitleEnabled() { - return true; - } - @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will @@ -213,11 +329,11 @@ public class MainActivity extends BaseActivity { * relative to its center position. An X-translation normally is done relative to a view's * left position. */ - private static float getFabPixelOffsetForXTranslation(FloatingActionButton fab) { + private float getFabPixelOffsetForXTranslation() { final int margin; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Since each side's margin is the same, any side's would do. - margin = ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).rightMargin; + margin = ((ViewGroup.MarginLayoutParams) mFab.getLayoutParams()).rightMargin; } else { // Pre-Lollipop has measurement issues with FAB margins. This is // probably as good as we can get to centering the FAB, without @@ -227,7 +343,7 @@ public class MainActivity extends BaseActivity { // X-translation is done relative to a view's left position; by adding // an offset of half the FAB's width, we effectively rebase the translation // relative to the view's center position. - return fab.getWidth() / 2f + margin; + return mFab.getWidth() / 2f + margin; } /** @@ -238,30 +354,30 @@ public class MainActivity extends BaseActivity { // We can't use an ArrayList because the structure reorganizes as elements are removed, // so page indices won't stay in sync with list indices. SparseArray allows you to have // gaps in your range of indices. - private final SparseArray mFragments = new SparseArray<>(getCount()); + private final SparseArray mFragments = new SparseArray<>(getCount()); - private SectionsPagerAdapter(FragmentManager fm) { + public SectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override - public BaseFragment getItem(int position) { + public Fragment getItem(int position) { // getItem is called to instantiate the fragment for the given page. switch (position) { - case PAGE_ALARMS: - return AlarmsFragment.newInstance(1); - case PAGE_TIMERS: - return new TimersFragment(); - case PAGE_STOPWATCH: - return new StopwatchFragment(); - default: - throw new IllegalStateException("No fragment can be instantiated for position " + position); + case PAGE_ALARMS: + return AlarmsFragment.newInstance(1); + case PAGE_TIMERS: + return new TimersFragment(); + case PAGE_STOPWATCH: + return new StopwatchFragment(); + default: + throw new IllegalStateException("No fragment can be instantiated for position " + position); } } @Override public Object instantiateItem(ViewGroup container, int position) { - BaseFragment fragment = (BaseFragment) super.instantiateItem(container, position); + Fragment fragment = (Fragment) super.instantiateItem(container, position); mFragments.put(position, fragment); return fragment; } @@ -278,80 +394,22 @@ public class MainActivity extends BaseActivity { return 3; } - BaseFragment getFragment(int position) { + // TODO: If you wish to have text labels for your tabs, then implement this method. +// @Override +// public CharSequence getPageTitle(int position) { +// switch (position) { +// case 0: +// return "SECTION 1"; +// case 1: +// return "SECTION 2"; +// case 2: +// return "SECTION 3"; +// } +// return null; +// } + + public Fragment getFragment(int position) { return mFragments.get(position); } } - - private static final class ModifyOffsetOnPageChangeListener extends ViewPager.SimpleOnPageChangeListener { - - private final FloatingActionButton mFab; - private final SectionsPagerAdapter mSectionsPagerAdapter; - private final ViewPager mViewPager; - - private ModifyOffsetOnPageChangeListener(final FloatingActionButton mFab, - final SectionsPagerAdapter mSectionsPagerAdapter, - final ViewPager mViewPager) { - this.mFab = mFab; - this.mSectionsPagerAdapter = mSectionsPagerAdapter; - this.mViewPager = mViewPager; - } - - /** - * @param position Either the current page position if the offset is increasing, - * or the previous page position if it is decreasing. - * @param positionOffset If increasing from [0, 1), scrolling right and position = currentPagePosition - * If decreasing from (1, 0], scrolling left and position = (currentPagePosition - 1) - */ - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - Log.d(TAG, String.format("pos = %d, posOffset = %f, posOffsetPixels = %d", - position, positionOffset, positionOffsetPixels)); - int pageBeforeLast = mSectionsPagerAdapter.getCount() - 2; - if (position <= pageBeforeLast) { - if (position < pageBeforeLast) { - // When the scrolling is due to tab selection between multiple tabs apart, - // this callback is called for each intermediate page, but each of those pages - // will briefly register a sparsely decreasing range of positionOffsets, always - // from (1, 0). As such, you would notice the FAB to jump back and forth between - // x-positions as each intermediate page is scrolled through. - // This is a visual optimization that ends the translation motion, immediately - // returning the FAB to its target position. - // TODO: The animation visibly skips to the end. We could interpolate - // intermediate x-positions if we cared to smooth it out. - mFab.setTranslationX(0); - } else { - // Initially, the FAB's translationX property is zero because, at its original - // position, it is not translated. setTranslationX() is relative to the view's - // left position, at its original position; this left position is taken to be - // the zero point of the coordinate system relative to this view. As your - // translationX value is increasingly negative, the view is translated left. - // But as translationX is decreasingly negative and down to zero, the view - // is translated right, back to its original position. - float translationX = positionOffsetPixels / -2f; - // NOTE: You MUST scale your own additional pixel offsets by positionOffset, - // or else the FAB will immediately translate by that many pixels, appearing - // to skip/jump. - translationX += positionOffset * getFabPixelOffsetForXTranslation(mFab); - mFab.setTranslationX(translationX); - } - } - } - - @Override - public void onPageSelected(int position) { - Log.d(TAG, "onPageSelected"); - if (position < mSectionsPagerAdapter.getCount() - 1) { - mFab.setImageResource(R.drawable.ic_add_24dp); - } - BaseFragment f = mSectionsPagerAdapter.getFragment(mViewPager.getCurrentItem()); - // NOTE: This callback is fired after a rotation, right after onStart(). - // Unfortunately, the FragmentManager handling the rotation has yet to - // tell our adapter to re-instantiate the Fragments, so our collection - // of fragments is empty. You MUST keep this check so we don't cause a NPE. - if (f != null) { - f.onPageSelected(); - } - } - } } diff --git a/app/src/main/res/drawable-hdpi/ic_settings.png b/app/src/main/res/drawable-hdpi/ic_settings.png deleted file mode 100644 index 37d89e8..0000000 Binary files a/app/src/main/res/drawable-hdpi/ic_settings.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings.png b/app/src/main/res/drawable-mdpi/ic_settings.png deleted file mode 100644 index d6c82c4..0000000 Binary files a/app/src/main/res/drawable-mdpi/ic_settings.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings.png b/app/src/main/res/drawable-xhdpi/ic_settings.png deleted file mode 100644 index b7cf057..0000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_settings.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings.png b/app/src/main/res/drawable-xxhdpi/ic_settings.png deleted file mode 100644 index d1f23c8..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_settings.png and /dev/null differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b16a169..da17cb6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,13 +1,13 @@ + tools:context="com.philliphsu.clock2.MainActivity" + android:id="@+id/main_content"> + app:layout_scrollFlags=""> + app:tabGravity="fill"/> @@ -45,8 +44,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" + app:srcCompat="@drawable/ic_add_24dp" android:layout_margin="@dimen/fab_margin" - android:tint="@android:color/white" - app:srcCompat="@drawable/ic_add_24dp"/> + android:tint="@android:color/white"/> diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index b31b741..9242898 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -6,6 +6,5 @@ android:id="@+id/action_settings" android:orderInCategory="100" android:title="@string/action_settings" - android:icon="@drawable/ic_settings" - app:showAsAction="always"/> + app:showAsAction="never"/> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index df6c008..3edeef4 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -86,9 +86,4 @@ 16dp 20sp 20dp - - - - 112dp - 56dp