diff options
author | Andy Mast <andy@cyngn.com> | 2014-06-30 10:20:25 -0700 |
---|---|---|
committer | Andy Mast <andy@cyngn.com> | 2014-07-03 16:51:08 -0700 |
commit | 9293ab6489ffca81afafa5c3ebf975b5b86f5d2c (patch) | |
tree | fd2cd0aaaf79ba7ca7582f44fb804fe0cf0046e1 | |
parent | df5c7c4e50911aeb64e2c50cfe8a93b704ead6f7 (diff) | |
download | packages_apps_ThemeChooser-9293ab6489ffca81afafa5c3ebf975b5b86f5d2c.zip packages_apps_ThemeChooser-9293ab6489ffca81afafa5c3ebf975b5b86f5d2c.tar.gz packages_apps_ThemeChooser-9293ab6489ffca81afafa5c3ebf975b5b86f5d2c.tar.bz2 |
Vertical center Themes by using a viewpager
Transitioning from a center collapsed view to an expanded view
requires a layout change. Animating children during a layout change
requires the children to be placed in a grandparent's ViewOverlay
so that they do not get clipped. A ListView cannot remove a child view,
so ViewOverlay cannot be used with ListView items, therefore a ViewPager
was used instead.
Change-Id: I665b89bf85d7466334db8a64bf37475e9e268efc
-rw-r--r-- | res/layout/v2_activity_main.xml | 29 | ||||
-rw-r--r-- | res/layout/v2_fragment_pager_list.xml | 26 | ||||
-rw-r--r-- | src/android/support/v4/view/ThemeViewPager.java (renamed from src/org/cyanogenmod/theme/chooserv2/ThemeViewPager.java) | 27 | ||||
-rw-r--r-- | src/org/cyanogenmod/theme/chooserv2/ChooserActivity.java | 114 | ||||
-rw-r--r-- | src/org/cyanogenmod/theme/chooserv2/PagerContainer.java | 63 | ||||
-rw-r--r-- | src/org/cyanogenmod/theme/chooserv2/ThemeFragment.java | 184 |
6 files changed, 274 insertions, 169 deletions
diff --git a/res/layout/v2_activity_main.xml b/res/layout/v2_activity_main.xml index 220f7ff..c8f9eba 100644 --- a/res/layout/v2_activity_main.xml +++ b/res/layout/v2_activity_main.xml @@ -1,20 +1,29 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/bg_bluegrid"> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center_vertical" + android:background="@drawable/bg_bluegrid"> + <TextView + android:id="@+id/theme_name" + android:layout_width="264dp" + android:layout_height="wrap_content" + android:layout_marginLeft="48dp" + android:paddingBottom="24dp" + android:textSize="24sp" + /> <org.cyanogenmod.theme.chooserv2.PagerContainer android:id="@+id/pager_container" android:layout_width="match_parent" - android:layout_height="match_parent" - > - <org.cyanogenmod.theme.chooserv2.ThemeViewPager + android:layout_height="300dp" + android:layout_gravity="center_vertical"> + <android.support.v4.view.ThemeViewPager android:id="@+id/viewpager" android:layout_width="264dp" android:layout_height="match_parent" - android:layout_gravity="center_horizontal" /> + android:layout_gravity="center_horizontal"/> </org.cyanogenmod.theme.chooserv2.PagerContainer> - -</RelativeLayout>
\ No newline at end of file +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/v2_fragment_pager_list.xml b/res/layout/v2_fragment_pager_list.xml index 59f46c7..cef65f9 100644 --- a/res/layout/v2_fragment_pager_list.xml +++ b/res/layout/v2_fragment_pager_list.xml @@ -3,12 +3,22 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> - <ListView android:id="@android:id/list" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center" - android:layout_gravity="center_vertical" - android:divider="@null" - android:scrollbars="none" - android:drawSelectorOnTop="false"/> + <ScrollView android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:layout_gravity="center_vertical" + android:divider="@null" + android:scrollbars="none" + android:drawSelectorOnTop="false"> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <include layout="@layout/v2item_statusbar"/> + <include layout="@layout/v2item_font"/> + <include layout="@layout/v2item_icon"/> + <include layout="@layout/v2item_navbar"/> + </LinearLayout> + </ScrollView> </LinearLayout>
\ No newline at end of file diff --git a/src/org/cyanogenmod/theme/chooserv2/ThemeViewPager.java b/src/android/support/v4/view/ThemeViewPager.java index f7b27e2..3fb1d61 100644 --- a/src/org/cyanogenmod/theme/chooserv2/ThemeViewPager.java +++ b/src/android/support/v4/view/ThemeViewPager.java @@ -13,14 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.cyanogenmod.theme.chooserv2; +package android.support.v4.view; import android.content.Context; -import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; import android.view.ViewConfiguration; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + public class ThemeViewPager extends ViewPager { private boolean mExpanded; @@ -78,4 +82,23 @@ public class ThemeViewPager extends ViewPager { return super.onTouchEvent(ev); } + + /** + * This method will return the view associated with a given position. This is neccessary + * because the index value in 'getChildAt(index)' does not neccessarily associate with + * the viewpager's position. + * + */ + public View getViewForPosition(int position) { + View view = null; + for(int i=0; i < getChildCount(); i++) { + View v = getChildAt(i); + ItemInfo info = infoForChild(v); + if (position == info.position) { + view = v; + break; + } + } + return view; + } } diff --git a/src/org/cyanogenmod/theme/chooserv2/ChooserActivity.java b/src/org/cyanogenmod/theme/chooserv2/ChooserActivity.java index 740f923..52ace61 100644 --- a/src/org/cyanogenmod/theme/chooserv2/ChooserActivity.java +++ b/src/org/cyanogenmod/theme/chooserv2/ChooserActivity.java @@ -15,47 +15,80 @@ */ package org.cyanogenmod.theme.chooserv2; +import android.content.Context; +import android.content.res.ThemeConfig; +import android.database.Cursor; import android.os.Bundle; +import android.provider.ThemesContract.ThemesColumns; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.PagerAdapter; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.view.ThemeViewPager; +import android.support.v4.view.ViewPager; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; -import android.widget.FrameLayout; +import android.widget.TextView; import org.cyanogenmod.theme.chooser.R; -public class ChooserActivity extends FragmentActivity { +public class ChooserActivity extends FragmentActivity + implements LoaderManager.LoaderCallbacks<Cursor> { + public static final String DEFAULT = ThemeConfig.HOLO_DEFAULT; private PagerContainer mContainer; private ThemeViewPager mPager; - private PagerAdapter mAdapter; + private TextView mThemeName; + private ThemesAdapter mAdapter; private boolean mExpanded = false; + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.v2_activity_main); mContainer = (PagerContainer) findViewById(R.id.pager_container); mPager = (ThemeViewPager) findViewById(R.id.viewpager); + mThemeName = (TextView) findViewById(R.id.theme_name); - mAdapter = new MyAdapter(getSupportFragmentManager()); mPager.setOnClickListener(mPagerClickListener); + mAdapter = new ThemesAdapter(this); mPager.setAdapter(mAdapter); + DisplayMetrics dm = getResources().getDisplayMetrics(); int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, dm); mPager.setPageMargin(margin); mPager.setOffscreenPageLimit(3); + + mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + public void onPageSelected(int position) { + updateThemeName(); + } + + public void onPageScrolled(int position, + float positionOffset, + int positionOffsetPixels) { + } + + public void onPageScrollStateChanged(int state) { + } + }); + + getSupportLoaderManager().initLoader(0, null, this); + } + + private void updateThemeName() { + int position = mPager.getCurrentItem(); + String name = mAdapter.getItemName(position); + mThemeName.setText(name); } private View.OnClickListener mPagerClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - final FrameLayout.LayoutParams layout = - (FrameLayout.LayoutParams) mPager.getLayoutParams(); mExpanded = !mExpanded; if (mExpanded) { mContainer.expand(); @@ -75,19 +108,70 @@ public class ChooserActivity extends FragmentActivity { return "android:switcher:"+R.id.viewpager+":"+pos; } - public static class MyAdapter extends FragmentPagerAdapter { - public MyAdapter(FragmentManager fm) { - super(fm); + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + mAdapter.notifyDataSetChanged(); + + updateThemeName(); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + mAdapter.swapCursor(null); + mAdapter.notifyDataSetChanged(); + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + String selection; + String selectionArgs[] = null; + selection = ThemesColumns.PRESENT_AS_THEME + "=?"; + selectionArgs = new String[] {"1"}; + + // sort in ascending order but make sure the "default" theme is always first + String sortOrder = "(" + ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " + + ThemesColumns.TITLE + " ASC"; + + return new CursorLoader(this, ThemesColumns.CONTENT_URI, null, selection, + selectionArgs, sortOrder); + } + + public class ThemesAdapter extends FragmentPagerAdapter { + private Cursor mCursor; + private Context mContext; + + public ThemesAdapter(Context context) { + super(getSupportFragmentManager()); + mContext = context; } @Override + public Fragment getItem(int position) { + mCursor.moveToPosition(position); + int pkgIdx = mCursor.getColumnIndex(ThemesColumns.PKG_NAME); + String pkgName = (String) mCursor.getString(pkgIdx); + return ThemeFragment.newInstance(pkgName); + } + public int getCount() { - return 3; + return mCursor == null ? 0 : mCursor.getCount(); } - @Override - public Fragment getItem(int position) { - return ThemeFragment.newInstance(); + public String getItemName(int position) { + mCursor.moveToPosition(position); + int pkgIdx = mCursor.getColumnIndex(ThemesColumns.PKG_NAME); + int titleIdx = mCursor.getColumnIndex(ThemesColumns.TITLE); + String pkgName = mCursor.getString(pkgIdx); + String title = DEFAULT.equals(pkgName) ? mContext.getString(R.string.holo) + : mCursor.getString(titleIdx); + return title; + } + + public void swapCursor(Cursor c) { + mCursor = c; } } } diff --git a/src/org/cyanogenmod/theme/chooserv2/PagerContainer.java b/src/org/cyanogenmod/theme/chooserv2/PagerContainer.java index d3c0f60..f58957f 100644 --- a/src/org/cyanogenmod/theme/chooserv2/PagerContainer.java +++ b/src/org/cyanogenmod/theme/chooserv2/PagerContainer.java @@ -26,13 +26,16 @@ package org.cyanogenmod.theme.chooserv2; import android.content.Context; import android.graphics.Point; +import android.support.v4.view.ThemeViewPager; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.ViewTreeObserver; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; +import android.widget.LinearLayout; /** * PagerContainer: A layout that displays a ViewPager with its children that are outside @@ -47,25 +50,23 @@ public class PagerContainer extends FrameLayout implements ViewPager.OnPageChang private ThemeViewPager mPager; private Point mCenter = new Point(); private Point mInitialTouch = new Point(); + private int mCollapsedHeight; boolean mNeedsRedraw = false; public PagerContainer(Context context) { - super(context); - init(); + this(context, null, 0); } public PagerContainer(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + this(context, attrs, 0); } public PagerContainer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - init(); - } - private void init() { + mCollapsedHeight = generateLayoutParams(attrs).height; + //Disable clipping of children so non-selected pages are visible setClipChildren(false); @@ -127,31 +128,53 @@ public class PagerContainer extends FrameLayout implements ViewPager.OnPageChang } public void expand() { - mPager.setExpanded(true); - int current = mPager.getCurrentItem(); - - if (current != 0) { - View lchild = mPager.getChildAt(current - 1); - animateChildOut(lchild, lchild.getX() - getWidth()); - } + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); + params.height = LinearLayout.LayoutParams.MATCH_PARENT; + setLayoutParams(params); - if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getChildAt(current + 1); - animateChildOut(rchild, rchild.getX() + getWidth()); - } + mPager.setExpanded(true); + final int current = mPager.getCurrentItem(); + final int prevY = (int) getY(); + + final ViewTreeObserver observer = mPager.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + if (current != 0) { + View lchild = mPager.getViewForPosition(current - 1); + lchild.setTranslationY(prevY - getY()); + animateChildOut(lchild, -getWidth()); + } + + if (current < mPager.getAdapter().getCount() - 1) { + View rchild = mPager.getViewForPosition(current + 1); + rchild.setTranslationY(prevY - getY()); + animateChildOut(rchild, getWidth()); + } + return false; + } + }); } public void collapse() { + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); + params.height = mCollapsedHeight; + setLayoutParams(params); + mPager.setExpanded(false); int current = mPager.getCurrentItem(); + final int prevY = (int) getY(); if (current != 0) { - View lchild = mPager.getChildAt(current - 1); + + View lchild = mPager.getViewForPosition(current - 1); + lchild.setTranslationY(0); animateChildIn(lchild); } if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getChildAt(current + 1); + View rchild = mPager.getViewForPosition(current + 1); + rchild.setTranslationY(0); animateChildIn(rchild); } } diff --git a/src/org/cyanogenmod/theme/chooserv2/ThemeFragment.java b/src/org/cyanogenmod/theme/chooserv2/ThemeFragment.java index 7c4540c..7be0a0d 100644 --- a/src/org/cyanogenmod/theme/chooserv2/ThemeFragment.java +++ b/src/org/cyanogenmod/theme/chooserv2/ThemeFragment.java @@ -15,7 +15,6 @@ */ package org.cyanogenmod.theme.chooserv2; -import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; @@ -24,10 +23,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.DecelerateInterpolator; -import android.widget.AbsListView; -import android.widget.BaseAdapter; -import android.widget.ListAdapter; -import android.widget.ListView; +import android.widget.ScrollView; import org.cyanogenmod.theme.chooser.R; @@ -38,11 +34,14 @@ public class ThemeFragment extends Fragment { public static final int ANIMATE_START_DELAY = 75; public static final int ANIMATE_DURATION = 500; public static final int ANIMATE_INTERPOLATE_FACTOR = 3; - private ListView mListView; - private ListAdapter mListAdapter; + private ScrollView mScrollView; + private ViewGroup mScrollContent; - static ThemeFragment newInstance() { + static ThemeFragment newInstance(String pkgName) { ThemeFragment f = new ThemeFragment(); + Bundle args = new Bundle(); + args.putString("pkgName", pkgName); + f.setArguments(args); return f; } @@ -69,136 +68,93 @@ public class ThemeFragment extends Fragment { View v = inflater.inflate(R.layout.v2_fragment_pager_list, container, false); v.setLayoutParams(params); - mListView = (ListView) v.findViewById(android.R.id.list); + mScrollView = (ScrollView) v.findViewById(android.R.id.list); + mScrollContent = (ViewGroup) mScrollView.getChildAt(0); return v; } public void expand() { - ((ThemePreviewAdapter) mListAdapter).setExpanded(true); - ((ThemePreviewAdapter) mListAdapter).notifyDataSetChanged(); + for (int i = 0; i < mScrollContent.getChildCount(); i++) { + View child = mScrollContent.getChildAt(i); + ViewGroup.LayoutParams layout = child.getLayoutParams(); + layout.height = 400; + child.setLayoutParams(layout); + } + mScrollContent.requestLayout(); animateChildren(); } public void collapse() { - ((ThemePreviewAdapter) mListAdapter).setExpanded(false); - ((ThemePreviewAdapter) mListAdapter).notifyDataSetChanged(); + for (int i = 0; i < mScrollContent.getChildCount(); i++) { + View child = mScrollContent.getChildAt(i); + ViewGroup.LayoutParams layout = child.getLayoutParams(); + layout.height = ViewGroup.LayoutParams.WRAP_CONTENT; + child.setLayoutParams(layout); + } + mScrollContent.requestLayout(); + animateChildren(); } // This will animate the children's vertical value between the existing and // new layout changes private void animateChildren() { - // Animate each child in the listview - final List<Float> prevYs = new ArrayList<Float>(); - for (int i = 0; i < mListView.getChildCount(); i++) { - final View v = mListView.getChildAt(i); - prevYs.add(v.getY()); - - final ViewTreeObserver observer = mListView.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - for (int i = 0; i < mListView.getChildCount(); i++) { - View v = mListView.getChildAt(i); - float prevY = prevYs.get(i); - final float endY = v.getY(); - v.setTranslationY(prevY - endY); - v.animate() - .setStartDelay(ANIMATE_START_DELAY) - .translationY(0) - .setDuration(ANIMATE_DURATION) - .setInterpolator( - new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)); - } - return false; - } - }); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mListAdapter = new ThemePreviewAdapter(getActivity(), null); - mListView.setAdapter(mListAdapter); - } + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); - public static class ThemePreviewAdapter extends BaseAdapter { - public static final String PLACEHOLDER_TAG = "placeholder"; - private static final int PLACEHOLDER_POSITION = 0; - - int[] layouts = {0, - R.layout.v2item_statusbar, - R.layout.v2item_font, - R.layout.v2item_icon, - R.layout.v2item_navbar}; - - private Context mContext; - private LayoutInflater mInflater; - private boolean mIsExpanded; - private List<String> mList; - - public ThemePreviewAdapter(Context context, List<String> list) { - mContext = context; - mInflater = LayoutInflater.from(context); - mList = list; + // Get the child's current location + final List<Float> prevYs = new ArrayList<Float>(); + for (int i = 0; i < mScrollContent.getChildCount(); i++) { + final View v = mScrollContent.getChildAt(i); + int[] pos = new int[2]; + v.getLocationInWindow(pos); + prevYs.add((float) pos[1]); } - public void setExpanded(boolean isExpanded) { - mIsExpanded = isExpanded; - } + // Grab the child's new location and animate from prev to current loc. + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + for (int i = mScrollContent.getChildCount() - 1; i >= 0; i--) { + final View v = mScrollContent.getChildAt(i); + + float prevY; + float endY; + if (i >= prevYs.size()) { + // View is being created + prevY = mScrollContent.getTop() + mScrollContent.getHeight(); + endY = v.getY(); + } else { + prevY = prevYs.get(i); + int[] endPos = new int[2]; + v.getLocationInWindow(endPos); + endY = endPos[1]; + } - @Override - public int getCount() { - return layouts.length; - } + v.setTranslationY((prevY - endY)); + root.getOverlay().add(v); - @Override - public Object getItem(int position) { - return mList.get(position); - } + v.animate() + .setStartDelay(ANIMATE_START_DELAY) + .translationY(0) + .setDuration(ANIMATE_DURATION) + .setInterpolator( + new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) + .withEndAction(new Runnable() { + public void run() { + root.getOverlay().remove(v); + mScrollContent.addView(v, 0); + } + }); - @Override - public long getItemId(int position) { - return position; - } - @Override - public View getView(int position, View convertView, ViewGroup parent) { - // This is just prototype code, needs to actually use convertView and - // view holder patterns - View view; - if (position == PLACEHOLDER_POSITION) { - view = new View(mContext); - view.setTag(PLACEHOLDER_TAG); - - int height = 0; - if (mIsExpanded) { - //TODO: There is probably a better way than hardcoding a height value - //Maybe seperate expanded/collapsed layouts for our child views, - //Or pass the expand mode through onMeasure. - height = 200; - } - - ListView.LayoutParams layout = - new ListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height); - view.setLayoutParams(layout); - } else { - view = mInflater.inflate(layouts[position], null, false); - if (mIsExpanded) { - AbsListView.LayoutParams layout = - (AbsListView.LayoutParams) view.getLayoutParams(); - if (layout == null) { - layout = new AbsListView.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, 400); - } - view.setLayoutParams(layout); } + return false; } - - return view; - } + }); } } |