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 /src/org | |
parent | df5c7c4e50911aeb64e2c50cfe8a93b704ead6f7 (diff) | |
download | packages_apps_ThemeChooser-9293ab6489ffca81afafa5c3ebf975b5b86f5d2c.tar.gz packages_apps_ThemeChooser-9293ab6489ffca81afafa5c3ebf975b5b86f5d2c.tar.bz2 packages_apps_ThemeChooser-9293ab6489ffca81afafa5c3ebf975b5b86f5d2c.zip |
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
Diffstat (limited to 'src/org')
4 files changed, 212 insertions, 230 deletions
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; - } + }); } } diff --git a/src/org/cyanogenmod/theme/chooserv2/ThemeViewPager.java b/src/org/cyanogenmod/theme/chooserv2/ThemeViewPager.java deleted file mode 100644 index f7b27e2..0000000 --- a/src/org/cyanogenmod/theme/chooserv2/ThemeViewPager.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.chooserv2; - -import android.content.Context; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.ViewConfiguration; - -public class ThemeViewPager extends ViewPager { - private boolean mExpanded; - - private boolean mIsDragging = false; - private float mSlop; - private float mLastX; - private float mLastY; - - public ThemeViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - initView(context); - } - - private void initView(Context context) { - mSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - } - - public void setExpanded(boolean expanded) { - mExpanded = expanded; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return true; - } - - @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; - } - - if (mExpanded) { - return true; - } - - return super.onTouchEvent(ev); - } -} |