diff options
author | Andy Mast <andy@cyngn.com> | 2014-05-12 11:05:33 -0700 |
---|---|---|
committer | Andy Mast <andy@cyngn.com> | 2014-05-15 14:42:56 +0000 |
commit | cf8f5e7f8c7195ae14827caa71e291d52d3e236e (patch) | |
tree | 515aac6ccfb101c1abb014218ccce1750a73b451 /src/org/cyanogenmod/theme | |
parent | f3684875dbc8dcaa74c588cd0ec3ef603c058208 (diff) | |
download | packages_apps_ThemeChooser-cf8f5e7f8c7195ae14827caa71e291d52d3e236e.zip packages_apps_ThemeChooser-cf8f5e7f8c7195ae14827caa71e291d52d3e236e.tar.gz packages_apps_ThemeChooser-cf8f5e7f8c7195ae14827caa71e291d52d3e236e.tar.bz2 |
Replace sliding panel with scrollview
This allows
1) Sliding of the drawer to happen in the theme viewpager
2) Seamless sliding from viewpager all the way to the bottom most
theme component checkbox (sounds).
Also clicking the viewpager now expands/minimizes the "drawer".
Before this patch the UX was a little awkward since the theme
components were in their own scrollview. A user could not scroll down at all
in the theme preview viewpager and would have to use two swipes to
get to the very bottom.
Change-Id: I1a0a30edfdd29d56db0aa40d06114f8bbaa39331
Diffstat (limited to 'src/org/cyanogenmod/theme')
4 files changed, 365 insertions, 4 deletions
diff --git a/src/org/cyanogenmod/theme/chooser/ChooserDetailFragment.java b/src/org/cyanogenmod/theme/chooser/ChooserDetailFragment.java index 75fe303..155e800 100644 --- a/src/org/cyanogenmod/theme/chooser/ChooserDetailFragment.java +++ b/src/org/cyanogenmod/theme/chooser/ChooserDetailFragment.java @@ -46,7 +46,7 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; -import com.sothree.slidinguppanel.SlidingupPanelLayout; +import org.cyanogenmod.theme.util.ChooserDetailScrollView; import java.util.ArrayList; import java.util.Collections; @@ -62,13 +62,34 @@ public class ChooserDetailFragment extends Fragment implements LoaderManager.Loa private static final int LOADER_ID_THEME_INFO = 0; private static final int LOADER_ID_APPLIED_THEME = 1; + // Drawer States + private static final int DRAWER_CLOSED = 0; + private static final int DRAWER_PARTIALLY_OPEN = 1; + private static final int DRAWER_MOSTLY_OPEN = 2; + private static final int DRAWER_OPEN = 3; + + // Threshold values in "percentage scrolled" to determine what state the drawer is in + // ex: User opens up the drawer a little bit, taking up 10% of the visible space. The + // drawer is now considered partially open + // because CLOSED_THRESHOLD < 10% < PARTIAL_OPEN_THRESHOLD + private static final int DRAWER_CLOSED_THRESHOLD = 5; + private static final int DRAWER_PARTIALLY_OPEN_THRESHOLD = 25; + private static final int DRAWER_MOSTLY_OPEN_THRESHOLD = 90; + private static final int DRAWER_OPEN_THRESHOLD = 100; + + // Where to scroll when moving to a new state + private static final int DRAWER_CLOSED_SCROLL_AMOUNT = 0; + private static final int DRAWER_PARTIALLY_OPEN_AMOUNT = 25; + private static final int DRAWER_MOSTLY_OPEN_AMOUNT = 75; + private static final int DRAWER_FULLY_OPEN_AMOUNT = 100; + private TextView mTitle; private TextView mAuthor; private Button mApply; private ViewPager mPager; private ThemeDetailPagerAdapter mPagerAdapter; private String mPkgName; - private SlidingupPanelLayout mSlidingPanel; + private ChooserDetailScrollView mSlidingPanel; private Handler mHandler; private Cursor mAppliedThemeCursor; @@ -120,8 +141,28 @@ public class ChooserDetailFragment extends Fragment implements LoaderManager.Loa mTitle = (TextView) v.findViewById(R.id.title); mAuthor = (TextView) v.findViewById(R.id.author); mPager = (ViewPager) v.findViewById(R.id.pager); + mPager.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + int state = getDrawerState(); + switch(state) { + case DRAWER_CLOSED: + smoothScrollDrawerTo(DRAWER_PARTIALLY_OPEN); + break; + case DRAWER_PARTIALLY_OPEN: + case DRAWER_MOSTLY_OPEN: + smoothScrollDrawerTo(DRAWER_OPEN); + break; + case DRAWER_OPEN: + smoothScrollDrawerTo(DRAWER_CLOSED); + break; + } + } + }); + mPagerAdapter = new ThemeDetailPagerAdapter(getChildFragmentManager()); mPager.setAdapter(mPagerAdapter); + mApply = (Button) v.findViewById(R.id.apply); mApply.setOnClickListener(new OnClickListener() { @@ -132,7 +173,7 @@ public class ChooserDetailFragment extends Fragment implements LoaderManager.Loa } }); - mSlidingPanel = (SlidingupPanelLayout) v.findViewById(R.id.sliding_layout); + mSlidingPanel = (ChooserDetailScrollView) v.findViewById(R.id.sliding_layout); // Find all the checkboxes for theme components (ex wallpaper) for (Map.Entry<String, Integer> entry : sComponentToId.entrySet()) { @@ -194,7 +235,9 @@ public class ChooserDetailFragment extends Fragment implements LoaderManager.Loa private Runnable mShowSlidingPanelRunnable = new Runnable() { @Override public void run() { - mSlidingPanel.expandPane(mSlidingPanel.getAnchorPoint()); + // Arbitrarily scroll a bit at the start + int height = mSlidingPanel.getHeight() / 4; + mSlidingPanel.smoothScrollTo(0, height); } }; @@ -523,4 +566,54 @@ public class ChooserDetailFragment extends Fragment implements LoaderManager.Loa return fragment; } } + + private void smoothScrollDrawerTo(int drawerState) { + int scrollPercentage = 0; + switch(drawerState) { + case DRAWER_CLOSED: + scrollPercentage = DRAWER_CLOSED_SCROLL_AMOUNT; + break; + case DRAWER_PARTIALLY_OPEN: + scrollPercentage = DRAWER_PARTIALLY_OPEN_AMOUNT; + break; + case DRAWER_MOSTLY_OPEN: + scrollPercentage = DRAWER_MOSTLY_OPEN_AMOUNT; + break; + case DRAWER_OPEN: + scrollPercentage = DRAWER_FULLY_OPEN_AMOUNT; + break; + default: + throw new IllegalArgumentException("Bad drawer state: " + drawerState); + } + + int visibleHeight = mSlidingPanel.getHeight(); + View handle = mSlidingPanel.getHandle(); + visibleHeight -= handle.getHeight(); + + int scrollY = scrollPercentage * visibleHeight / 100; + mSlidingPanel.smoothScrollTo(0, scrollY); + } + + private int getDrawerState() { + // Scroll between 3 different heights when pager is clicked + int visibleHeight = mSlidingPanel.getHeight(); + View handle = mSlidingPanel.getHandle(); + visibleHeight -= handle.getHeight(); + int scrollY = mSlidingPanel.getScrollY(); + int percentageScrolled = (scrollY * 100) / (visibleHeight); + + //Check if we are bottom of scroll + View view = (View) mSlidingPanel.getChildAt(0); + boolean isAtBottom = (view.getBottom() - (mSlidingPanel.getHeight() + scrollY)) == 0; + + if (percentageScrolled < DRAWER_CLOSED_THRESHOLD && !isAtBottom) { + return DRAWER_CLOSED; + } else if (percentageScrolled < DRAWER_PARTIALLY_OPEN_THRESHOLD && !isAtBottom) { + return DRAWER_PARTIALLY_OPEN; + } else if (percentageScrolled < DRAWER_MOSTLY_OPEN_THRESHOLD && !isAtBottom) { + return DRAWER_MOSTLY_OPEN; + } + + return DRAWER_OPEN; + } } diff --git a/src/org/cyanogenmod/theme/util/ChooserDetailLinearLayout.java b/src/org/cyanogenmod/theme/util/ChooserDetailLinearLayout.java new file mode 100644 index 0000000..59b7340 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/ChooserDetailLinearLayout.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.cyanogenmod.theme.util; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + + +/** + * This class will layout its children in a very specific way. It assumes it is a child of a + * scroll view. + * + * The first two children are assumed to be the "theme preview" and the "drawer handle" which will + * be visible. These children will be restricted in their size since they must be entirely visible. + * The third child will be shown as the user scrolls down and can take as much space as it needs. + */ +public class ChooserDetailLinearLayout extends LinearLayout { + + // The smaller this value the greater the effect + public final int PARALLAX_CONSTANT = 2; + + // Child indices + public final int THEME_PREVIEW_INDEX = 0; + public final int DRAWER_HANDLE_INDEX = 1; + public final int DRAWER_CONTENT_INDEX = 2; + + private ViewPager mPager; + + public ChooserDetailLinearLayout(Context context) { + super(context); + } + + public ChooserDetailLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ChooserDetailLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Assumes the heightMeasureSpec will be exact. This exact data will be used + * to determine the visible page size. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + // Drawer handle is just author/title. Should get whatever space it needs + View drawerHandle = getChildAt(DRAWER_HANDLE_INDEX); + measureChildWithMargins(drawerHandle, widthMeasureSpec, 0, heightMeasureSpec, 0); + + // Give the theme preview the remainder + View themePreview = getChildAt(THEME_PREVIEW_INDEX); + int usedHeight = drawerHandle.getMeasuredHeight(); + measureChildWithMargins(themePreview, widthMeasureSpec, 0, heightMeasureSpec, usedHeight); + + // Give the drawer content as much as space as it needs. + View drawerContent = getChildAt(DRAWER_CONTENT_INDEX); + final MarginLayoutParams lp = (MarginLayoutParams) drawerContent.getLayoutParams(); + int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); + int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + drawerContent.measure(childWidthSpec, childHeightSpec); + + // Measure ourself + int width = drawerHandle.getMeasuredWidth(); + int height = themePreview.getMeasuredHeight() + + drawerHandle.getMeasuredHeight() + drawerContent.getMeasuredHeight(); + setMeasuredDimension(width, height); + } + + public void onScrollChanged(int l, int t, int oldl, int oldt) { + // Give a parallax effect to the theme preview by scrolling in the + // opposite direction of the scrollview + int yScroll = (int) (-t / PARALLAX_CONSTANT); + getChildAt(THEME_PREVIEW_INDEX).scrollTo(0, yScroll); + } + + View getHandle() { + return getChildAt(DRAWER_HANDLE_INDEX); + } +} diff --git a/src/org/cyanogenmod/theme/util/ChooserDetailScrollView.java b/src/org/cyanogenmod/theme/util/ChooserDetailScrollView.java new file mode 100644 index 0000000..8734cae --- /dev/null +++ b/src/org/cyanogenmod/theme/util/ChooserDetailScrollView.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.cyanogenmod.theme.util; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ScrollView; + +/** + * A custom ScrollView capable of alerting its children when a scroll has changed. + * + * This custom view will also measure its children differently. Normally a child in scrollview + * receives an UNSPECIFIED MeasureSpec so that the child will size itself as large as it needs to be. + * + * In our case we want to have a unique layout where the first full page of the scrollview + * has very particular content + * ie the "drawer title/author", and the viewpager taking the remaining part of the screen", with the + * theme component checkboxes taking as much space as it needs. + * + **/ +public class ChooserDetailScrollView extends ScrollView { + private float xDistance; + private float yDistance; + private float lastX; + private float lastY; + + public ChooserDetailScrollView(Context context) { + super(context); + } + + public ChooserDetailScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ChooserDetailScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + + widthUsed, lp.width); + child.measure(childWidthMeasureSpec, parentHeightMeasureSpec); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + xDistance = yDistance = 0f; + lastX = ev.getX(); + lastY = ev.getY(); + break; + case MotionEvent.ACTION_MOVE: + final float curX = ev.getX(); + final float curY = ev.getY(); + xDistance = Math.abs(curX - lastX); + yDistance = Math.abs(curY - lastY); + lastX = curX; + lastY = curY; + if(xDistance > yDistance) return false; + } + + return super.onInterceptTouchEvent(ev); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + ((ChooserDetailLinearLayout)getChildAt(0)).onScrollChanged(l, t, oldl, oldt); + } + + public View getHandle() { + return ((ChooserDetailLinearLayout)getChildAt(0)).getHandle(); + } +} diff --git a/src/org/cyanogenmod/theme/util/ClickableViewPager.java b/src/org/cyanogenmod/theme/util/ClickableViewPager.java new file mode 100644 index 0000000..98364ea --- /dev/null +++ b/src/org/cyanogenmod/theme/util/ClickableViewPager.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.cyanogenmod.theme.util; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +/** + * Adds click functionality for the view pager since it not normally clickable + * and focus/clickable attributes have no impact on it. + */ +public class ClickableViewPager extends ViewPager { + private boolean mIsDragging = false; + private float mSlop; + private float mLastX; + private float mLastY; + + public ClickableViewPager(Context context) { + super(context); + initView(context); + } + + public ClickableViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + private void initView(Context context) { + mSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mLastX = ev.getX(); + mLastY = ev.getY(); + break; + case MotionEvent.ACTION_MOVE: + float xDist = Math.abs(mLastX - ev.getX()); + float yDist = Math.abs(mLastY - ev.getY()); + if (xDist > mSlop || yDist > mSlop) { + mIsDragging = true; + } + break; + case MotionEvent.ACTION_CANCEL: + mIsDragging = false; + break; + case MotionEvent.ACTION_UP: + if (!mIsDragging) { + performClick(); + } + mIsDragging = false; + break; + } + return super.onTouchEvent(ev); + } +}
\ No newline at end of file |