diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java index 032f02b33..34a62ff67 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java @@ -25,6 +25,7 @@ import android.graphics.Color; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import org.mariotaku.querybuilder.Columns.Column; import org.mariotaku.querybuilder.Expression; @@ -144,24 +145,37 @@ public class ParcelableAccount implements Parcelable { return list.toArray(new ParcelableAccount[list.size()]); } - public static ParcelableAccount[] getAccounts(final Context context, final long[] accountIds) { + @NonNull + public static ParcelableAccount[] getAccounts(@Nullable final Context context, @Nullable final long[] accountIds) { if (context == null) return new ParcelableAccount[0]; final String where = accountIds != null ? Expression.in(new Column(Accounts.ACCOUNT_ID), new RawItemArray(accountIds)).getSQL() : null; final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, Accounts.COLUMNS_NO_CREDENTIALS, where, null, null); if (cur == null) return new ParcelableAccount[0]; + return getAccounts(cur, new Indices(cur)); + } + + + @NonNull + public static ParcelableAccount[] getAccounts(@Nullable final Cursor cursor) { + if (cursor == null) return new ParcelableAccount[0]; + return getAccounts(cursor, new Indices(cursor)); + } + + @NonNull + public static ParcelableAccount[] getAccounts(@Nullable final Cursor cursor,@Nullable final Indices indices) { + if (cursor == null || indices == null) return new ParcelableAccount[0]; try { - final Indices idx = new Indices(cur); - cur.moveToFirst(); - final ParcelableAccount[] names = new ParcelableAccount[cur.getCount()]; - while (!cur.isAfterLast()) { - names[cur.getPosition()] = new ParcelableAccount(cur, idx); - cur.moveToNext(); + cursor.moveToFirst(); + final ParcelableAccount[] names = new ParcelableAccount[cursor.getCount()]; + while (!cursor.isAfterLast()) { + names[cursor.getPosition()] = new ParcelableAccount(cursor, indices); + cursor.moveToNext(); } return names; } finally { - cur.close(); + cursor.close(); } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java index 809a99658..375095b4b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java @@ -19,6 +19,10 @@ package org.mariotaku.twidere.fragment.support; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; @@ -29,13 +33,18 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.Matrix; import android.graphics.PorterDuff.Mode; import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v4.util.LongSparseArray; import android.support.v4.util.Pair; import android.support.v4.view.MenuItemCompat; import android.support.v7.internal.view.SupportMenuInflater; @@ -52,6 +61,7 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageView; @@ -78,17 +88,19 @@ import org.mariotaku.twidere.adapter.ArrayAdapter; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.menu.SupportAccountActionProvider; import org.mariotaku.twidere.model.ParcelableAccount; -import org.mariotaku.twidere.model.ParcelableAccount.Indices; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; import org.mariotaku.twidere.util.CompareUtils; import org.mariotaku.twidere.util.ImageLoaderWrapper; import org.mariotaku.twidere.util.ThemeUtils; +import org.mariotaku.twidere.util.TransitionUtils; import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.util.content.SupportFragmentReloadCursorObserver; import org.mariotaku.twidere.view.ShapedImageView; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import static org.mariotaku.twidere.util.Utils.openUserFavorites; @@ -114,6 +126,7 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement private View mAccountSelectorView; private RecyclerView mAccountsSelector; private ImageView mAccountProfileBannerView; + private ImageView mFloatingProfileImageSnapshotView; private ShapedImageView mAccountProfileImageView; private TextView mAccountProfileNameView, mAccountProfileScreenNameView; private ActionMenuView mAccountsToggleMenu; @@ -122,6 +135,7 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement private Context mThemedContext; private ImageLoaderWrapper mImageLoader; private SupportAccountActionProvider mAccountActionProvider; + private boolean mSwitchAccountAnimationPlaying; @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { @@ -180,38 +194,30 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement final Menu menu = mAccountsToggleMenu.getMenu(); mAccountActionProvider = (SupportAccountActionProvider) MenuItemCompat.getActionProvider(menu.findItem(MENU_SELECT_ACCOUNT)); mAccountActionProvider.setExclusive(false); - final ArrayList accounts = new ArrayList<>(); + final ParcelableAccount[] accounts = ParcelableAccount.getAccounts(data); final Set activatedIds = new HashSet<>(); - if (data != null) { - data.moveToFirst(); - final Indices indices = new Indices(data); - long defaultId = -1; - while (!data.isAfterLast()) { - final ParcelableAccount account = new ParcelableAccount(data, indices); - accounts.add(account); - if (account.is_activated) { - if (defaultId < 0) { - defaultId = account.account_id; - } - activatedIds.add(account.account_id); + long defaultId = -1; + for (ParcelableAccount account : accounts) { + if (account.is_activated) { + if (defaultId < 0) { + defaultId = account.account_id; } - data.moveToNext(); - } - if (mAccountsAdapter.getSelectedAccountId() <= 0) { - mAccountsAdapter.setSelectedAccountId(mPreferences.getLong(KEY_DEFAULT_ACCOUNT_ID, defaultId)); + activatedIds.add(account.account_id); } } - mAccountActionProvider.setAccounts(accounts.toArray(new ParcelableAccount[accounts.size()])); + mAccountsAdapter.setAccounts(accounts); + if (mAccountsAdapter.getSelectedAccountId() <= 0) { + mAccountsAdapter.setSelectedAccountId(mPreferences.getLong(KEY_DEFAULT_ACCOUNT_ID, defaultId)); + } + mAccountActionProvider.setAccounts(accounts); mAccountActionProvider.setSelectedAccountIds(ArrayUtils.toPrimitive(activatedIds.toArray(new Long[activatedIds.size()]))); - mAccountsAdapter.changeCursor(data); - updateAccountOptionsSeparatorLabel(); + updateAccountOptionsSeparatorLabel(null); updateDefaultAccountState(); } @Override public void onLoaderReset(final Loader loader) { - mAccountsAdapter.changeCursor(null); } @Override @@ -345,9 +351,11 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement layoutManager.setStackFromEnd(true); mAccountsSelector.setLayoutManager(layoutManager); mAccountsSelector.setAdapter(mAccountsAdapter); + mAccountsSelector.setItemAnimator(null); mAccountProfileContainer = mAccountSelectorView.findViewById(R.id.profile_container); mAccountProfileImageView = (ShapedImageView) mAccountSelectorView.findViewById(R.id.profile_image); mAccountProfileBannerView = (ImageView) mAccountSelectorView.findViewById(R.id.account_profile_banner); + mFloatingProfileImageSnapshotView = (ImageView) mAccountSelectorView.findViewById(R.id.floating_profile_image_snapshot); mAccountProfileNameView = (TextView) mAccountSelectorView.findViewById(R.id.name); mAccountProfileScreenNameView = (TextView) mAccountSelectorView.findViewById(R.id.screen_name); mAccountsToggleMenu = (ActionMenuView) mAccountSelectorView.findViewById(R.id.toggle_menu); @@ -412,19 +420,102 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement return mThemedContext = new ContextThemeWrapper(context, themeResource); } - private void onAccountSelected(ParcelableAccount account) { - mAccountsAdapter.setSelectedAccountId(account.account_id); - updateAccountOptionsSeparatorLabel(); + private void onAccountSelected(AccountProfileImageViewHolder holder, final ParcelableAccount account) { + if (mSwitchAccountAnimationPlaying) return; + final ImageView snapshotView = mFloatingProfileImageSnapshotView; + final ShapedImageView profileImageView = mAccountProfileImageView; + final ShapedImageView clickedImageView = holder.getIconView(); + final Matrix matrix = new Matrix(); + final Rect tempRect = new Rect(); + clickedImageView.getGlobalVisibleRect(tempRect); + final RectF sourceBounds = new RectF(tempRect); + profileImageView.getGlobalVisibleRect(tempRect); + final RectF destBounds = new RectF(tempRect); + final float finalScale = destBounds.width() / sourceBounds.width(); + final Bitmap snapshotBitmap = TransitionUtils.createViewBitmap(clickedImageView, matrix, + new RectF(0, 0, sourceBounds.width(), sourceBounds.height())); + final ViewGroup.LayoutParams lp = snapshotView.getLayoutParams(); + lp.width = clickedImageView.getWidth(); + lp.height = clickedImageView.getHeight(); + snapshotView.setLayoutParams(lp); + // Copied from MaterialNavigationDrawer: https://github.com/madcyph3r/AdvancedMaterialDrawer/ + AnimatorSet set = new AnimatorSet(); + snapshotView.setPivotX(0); + snapshotView.setPivotY(0); + snapshotView.setX(sourceBounds.left); + snapshotView.setY(sourceBounds.top); + set.play(ObjectAnimator.ofFloat(snapshotView, View.X, sourceBounds.left, destBounds.left)) + .with(ObjectAnimator.ofFloat(snapshotView, View.Y, sourceBounds.top, destBounds.top)) + .with(ObjectAnimator.ofFloat(snapshotView, View.SCALE_X, 1, finalScale)) + .with(ObjectAnimator.ofFloat(snapshotView, View.SCALE_Y, 1, finalScale)) + .with(ObjectAnimator.ofFloat(profileImageView, View.ALPHA, 1, 0)) + .with(ObjectAnimator.ofFloat(clickedImageView, View.SCALE_X, 0, 1)) + .with(ObjectAnimator.ofFloat(clickedImageView, View.SCALE_Y, 0, 1)); + final long animationTransition = 400; + set.setDuration(animationTransition); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListener() { + + private Drawable clickedDrawable; + private int[] clickedColors; + + @Override + public void onAnimationStart(Animator animation) { + snapshotView.setVisibility(View.VISIBLE); + snapshotView.setImageBitmap(snapshotBitmap); + final Drawable profileDrawable = profileImageView.getDrawable(); + clickedDrawable = clickedImageView.getDrawable(); + clickedColors = clickedImageView.getBorderColors(); + final ParcelableAccount oldSelectedAccount = mAccountsAdapter.getSelectedAccount(); + mImageLoader.displayDashboardProfileImage(clickedImageView, + oldSelectedAccount.profile_image_url, profileDrawable); +// mImageLoader.displayDashboardProfileImage(profileImageView, +// account.profile_image_url, clickedDrawable); + clickedImageView.setBorderColors(profileImageView.getBorderColors()); + mSwitchAccountAnimationPlaying = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + finishAnimation(); + } + + private void finishAnimation() { + mAccountsAdapter.setSelectedAccountId(account.account_id); + updateAccountOptionsSeparatorLabel(clickedDrawable); + snapshotView.setVisibility(View.INVISIBLE); + snapshotView.setImageDrawable(null); + profileImageView.setImageDrawable(clickedDrawable); + profileImageView.setBorderColors(clickedColors); + profileImageView.setAlpha(1f); + clickedImageView.setScaleX(1); + clickedImageView.setScaleY(1); + clickedImageView.setAlpha(1f); + mSwitchAccountAnimationPlaying = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + finishAnimation(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + set.start(); } - private void updateAccountOptionsSeparatorLabel() { + private void updateAccountOptionsSeparatorLabel(Drawable profileImageSnapshot) { final ParcelableAccount account = mAccountsAdapter.getSelectedAccount(); if (account == null) { return; } mAccountProfileNameView.setText(account.name); mAccountProfileScreenNameView.setText("@" + account.screen_name); - mImageLoader.displayProfileImage(mAccountProfileImageView, account.profile_image_url); + mImageLoader.displayDashboardProfileImage(mAccountProfileImageView, + account.profile_image_url, profileImageSnapshot); mAccountProfileImageView.setBorderColors(account.color); final int bannerWidth = mAccountProfileBannerView.getWidth(); final Resources res = getResources(); @@ -483,9 +574,13 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement icon = (ShapedImageView) itemView.findViewById(android.R.id.icon); } + public ShapedImageView getIconView() { + return icon; + } + @Override public void onClick(View v) { - adapter.dispatchItemSelected(getPosition()); + adapter.dispatchItemSelected(this); } } @@ -494,62 +589,95 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement private final LayoutInflater mInflater; private final ImageLoaderWrapper mImageLoader; private final AccountsDashboardFragment mFragment; - private Cursor mCursor; - private Indices mIndices; - private long mSelectedAccountId; - private int mSelectedAccountIndex; + private ParcelableAccount[] mAccounts; + private ParcelableAccount[] mInternalAccounts; AccountSelectorAdapter(Context context, AccountsDashboardFragment fragment) { mInflater = LayoutInflater.from(context); mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); mFragment = fragment; + setHasStableIds(true); } - public void changeCursor(Cursor cursor) { - mCursor = cursor; - if (cursor != null) { - mIndices = new Indices(cursor); + private static int indexOfAccount(List accounts, long accountId) { + for (int i = 0, j = accounts.size(); i < j; i++) { + if (accounts.get(i).account_id == accountId) return i; + } + return -1; + } + + public void setAccounts(ParcelableAccount[] accounts) { + mAccounts = accounts; + if (accounts != null) { + final ParcelableAccount[] previousAccounts = mInternalAccounts; + mInternalAccounts = new ParcelableAccount[accounts.length]; + int tempIdx = 0; + final List tempList = Arrays.asList(accounts); + if (previousAccounts != null) { + for (ParcelableAccount previousAccount : previousAccounts) { + final int idx = indexOfAccount(tempList, previousAccount.account_id); + if (idx >= 0) { + mInternalAccounts[tempIdx++] = tempList.remove(idx); + } + } + } + for (ParcelableAccount account : tempList) { + mInternalAccounts[tempIdx++] = account; + } + } else { + mInternalAccounts = null; } - updateSelectedAccountIndex(); notifyDataSetChanged(); } - private void updateSelectedAccountIndex() { - final Cursor c = mCursor; - final Indices i = mIndices; - mSelectedAccountIndex = -1; - if (c != null && i != null && c.moveToFirst()) { - while (!c.isAfterLast()) { - if (c.getLong(mIndices.account_id) == mSelectedAccountId) { - mSelectedAccountIndex = c.getPosition(); - break; - } - c.moveToNext(); + + public ParcelableAccount getAdapterAccount(int adapterPosition) { + if (mInternalAccounts == null || mInternalAccounts.length < 1) { + return null; + } + return mInternalAccounts[adapterPosition + 1]; + } + + private final LongSparseArray positionMap = new LongSparseArray<>(); + + private void swap(long fromId, long toId) { + int fromIdx = -1, toIdx = -1; + for (int i = 0, j = mInternalAccounts.length; i < j; i++) { + final ParcelableAccount account = mInternalAccounts[i]; + if (account.account_id == fromId) { + fromIdx = i; + } + if (account.account_id == toId) { + toIdx = i; } } + if (fromIdx < 0 || toIdx < 0) return; + final ParcelableAccount temp = mInternalAccounts[toIdx]; + mInternalAccounts[toIdx] = mInternalAccounts[fromIdx]; + mInternalAccounts[fromIdx] = temp; + notifyDataSetChanged(); } public ParcelableAccount getSelectedAccount() { - final Cursor c = mCursor; - final Indices i = mIndices; - if (c != null && i != null && c.moveToFirst()) { - while (!c.isAfterLast()) { - if (c.getLong(mIndices.account_id) == mSelectedAccountId) - return new ParcelableAccount(c, mIndices); - c.moveToNext(); - } + if (mInternalAccounts == null || mInternalAccounts.length < 0) { + return null; } - return null; + return mInternalAccounts[0]; } public long getSelectedAccountId() { - return mSelectedAccountId; + return getSelectedAccount().account_id; } public void setSelectedAccountId(long accountId) { - mSelectedAccountId = accountId; - updateSelectedAccountIndex(); - notifyDataSetChanged(); + final ParcelableAccount selectedAccount = getSelectedAccount(); + if (selectedAccount == null) return; + swap(accountId, selectedAccount.account_id); + } + + @Override + public long getItemId(int position) { + return getAdapterAccount(position).account_id; } @Override @@ -560,31 +688,22 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement @Override public void onBindViewHolder(AccountProfileImageViewHolder holder, int position) { - final Cursor c = mCursor; - if (mSelectedAccountIndex != -1 && position >= mSelectedAccountIndex) { - c.moveToPosition(position + 1); - } else { - c.moveToPosition(position); - } - holder.itemView.setAlpha(c.getInt(mIndices.is_activated) == 1 ? 1 : 0.5f); - mImageLoader.displayProfileImage(holder.icon, c.getString(mIndices.profile_image_url)); - holder.icon.setBorderColor(c.getInt(mIndices.color)); +// holder.itemView.setAlpha(c.getInt(mIndices.is_activated) == 1 ? 1 : 0.5f); + final ParcelableAccount account = getAdapterAccount(position); + mImageLoader.cancelDisplayTask(holder.icon); +// holder.icon.setImageDrawable(null); + mImageLoader.displayDashboardProfileImage(holder.icon, account.profile_image_url, null); + holder.icon.setBorderColor(account.color); } @Override public int getItemCount() { - if (mCursor == null) return 0; - return Math.max(mCursor.getCount() - 1, 0); + if (mInternalAccounts == null || mInternalAccounts.length == 0) return 0; + return mInternalAccounts.length - 1; } - private void dispatchItemSelected(int position) { - final Cursor c = mCursor; - if (mSelectedAccountIndex != -1 && position >= mSelectedAccountIndex) { - c.moveToPosition(position + 1); - } else { - c.moveToPosition(position); - } - mFragment.onAccountSelected(new ParcelableAccount(c, mIndices)); + private void dispatchItemSelected(AccountProfileImageViewHolder holder) { + mFragment.onAccountSelected(holder, getAdapterAccount(holder.getAdapterPosition())); } } @@ -643,7 +762,7 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement final TextView text1 = (TextView) view.findViewById(android.R.id.text1); final ImageView icon = (ImageView) view.findViewById(android.R.id.icon); text1.setText(option.name); - icon.setImageDrawable(icon.getResources().getDrawable(option.icon)); + icon.setImageDrawable(ResourcesCompat.getDrawable(icon.getResources(), option.icon, null)); icon.setColorFilter(mActionIconColor, Mode.SRC_ATOP); return view; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ImageLoaderWrapper.java b/twidere/src/main/java/org/mariotaku/twidere/util/ImageLoaderWrapper.java index 7296d8656..65cd27e2f 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/ImageLoaderWrapper.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ImageLoaderWrapper.java @@ -20,9 +20,11 @@ package org.mariotaku.twidere.util; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.widget.ImageView; import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.DisplayImageOptions.Builder; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; @@ -38,6 +40,7 @@ public class ImageLoaderWrapper implements Constants { private final ImageLoader mImageLoader; private final DisplayImageOptions mProfileImageDisplayOptions; + private final DisplayImageOptions mDashboardProfileImageDisplayOptions; private final DisplayImageOptions mOvalProfileImageDisplayOptions; private final DisplayImageOptions mImageDisplayOptions, mBannerDisplayOptions; @@ -72,11 +75,17 @@ public class ImageLoaderWrapper implements Constants { bannerOptsBuilder.cacheOnDisk(true); bannerOptsBuilder.bitmapConfig(Bitmap.Config.RGB_565); bannerOptsBuilder.displayer(new FadeInBitmapDisplayer(200, true, true, true)); + final DisplayImageOptions.Builder dashboardProfileOptsBuilder = new DisplayImageOptions.Builder(); +// dashboardProfileOptsBuilder.showImageOnLoading(android.R.color.transparent); + dashboardProfileOptsBuilder.cacheInMemory(true); + dashboardProfileOptsBuilder.cacheOnDisk(true); + dashboardProfileOptsBuilder.bitmapConfig(Bitmap.Config.RGB_565); mProfileImageDisplayOptions = profileOptsBuilder.build(); mOvalProfileImageDisplayOptions = ovalProfileOptsBuilder.build(); mImageDisplayOptions = imageOptsBuilder.build(); mBannerDisplayOptions = bannerOptsBuilder.build(); + mDashboardProfileImageDisplayOptions = dashboardProfileOptsBuilder.build(); } public void clearFileCache() { @@ -120,6 +129,18 @@ public class ImageLoaderWrapper implements Constants { mImageLoader.displayImage(url, view, mProfileImageDisplayOptions); } + public void displayDashboardProfileImage(final ImageView view, final String url, Drawable drawableOnLoading) { + if (drawableOnLoading != null) { + final Builder builder = new Builder(); + builder.cloneFrom(mDashboardProfileImageDisplayOptions); + builder.showImageOnLoading(drawableOnLoading); + builder.showImageOnFail(drawableOnLoading); + mImageLoader.displayImage(url, view, builder.build()); + return; + } + mImageLoader.displayImage(url, view, mDashboardProfileImageDisplayOptions); + } + public void displayImage(final ImageView view, final String url, DisplayImageOptions options) { mImageLoader.displayImage(url, view, options); diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/TransitionUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/TransitionUtils.java new file mode 100644 index 000000000..e0e3c50ab --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/util/TransitionUtils.java @@ -0,0 +1,135 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.mariotaku.twidere.util; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.TypeEvaluator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.View; + +/** + * Static utility methods for Transitions. + * + * @hide + */ +public class TransitionUtils { + private static int MAX_IMAGE_SIZE = (1024 * 1024); + + static Animator mergeAnimators(Animator animator1, Animator animator2) { + if (animator1 == null) { + return animator2; + } else if (animator2 == null) { + return animator1; + } else { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(animator1, animator2); + return animatorSet; + } + } + + /** + * Get a copy of bitmap of given drawable, return null if intrinsic size is zero + */ + public static Bitmap createDrawableBitmap(Drawable drawable) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + if (width <= 0 || height <= 0) { + return null; + } + float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (width * height)); + if (drawable instanceof BitmapDrawable && scale == 1f) { + // return same bitmap if scale down not needed + return ((BitmapDrawable) drawable).getBitmap(); + } + int bitmapWidth = (int) (width * scale); + int bitmapHeight = (int) (height * scale); + Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Rect existingBounds = drawable.getBounds(); + int left = existingBounds.left; + int top = existingBounds.top; + int right = existingBounds.right; + int bottom = existingBounds.bottom; + drawable.setBounds(0, 0, bitmapWidth, bitmapHeight); + drawable.draw(canvas); + drawable.setBounds(left, top, right, bottom); + return bitmap; + } + + /** + * Creates a Bitmap of the given view, using the Matrix matrix to transform to the local + * coordinates. matrix will be modified during the bitmap creation. + *

+ *

If the bitmap is large, it will be scaled uniformly down to at most 1MB size.

+ * + * @param view The view to create a bitmap for. + * @param matrix The matrix converting the view local coordinates to the coordinates that + * the bitmap will be displayed in. matrix will be modified before + * returning. + * @param bounds The bounds of the bitmap in the destination coordinate system (where the + * view should be presented. Typically, this is matrix.mapRect(viewBounds); + * @return A bitmap of the given view or null if bounds has no width or height. + */ + public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds) { + Bitmap bitmap = null; + int bitmapWidth = Math.round(bounds.width()); + int bitmapHeight = Math.round(bounds.height()); + if (bitmapWidth > 0 && bitmapHeight > 0) { + float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight)); + bitmapWidth *= scale; + bitmapHeight *= scale; + matrix.postTranslate(-bounds.left, -bounds.top); + matrix.postScale(scale, scale); + bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.concat(matrix); + view.draw(canvas); + } + return bitmap; + } + + public static class MatrixEvaluator implements TypeEvaluator { + + float[] mTempStartValues = new float[9]; + + float[] mTempEndValues = new float[9]; + + Matrix mTempMatrix = new Matrix(); + + @Override + public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) { + startValue.getValues(mTempStartValues); + endValue.getValues(mTempEndValues); + for (int i = 0; i < 9; i++) { + float diff = mTempEndValues[i] - mTempStartValues[i]; + mTempEndValues[i] = mTempStartValues[i] + (fraction * diff); + } + mTempMatrix.setValues(mTempEndValues); + return mTempMatrix; + } + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java b/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java index feac7e2a5..d837bcc28 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java @@ -228,6 +228,10 @@ public class ShapedImageView extends ImageView { } + public int[] getBorderColors() { + return mBorderColors; + } + @ShapeStyle public int getStyle() { return mStyle; diff --git a/twidere/src/main/res/layout/header_drawer_account_selector.xml b/twidere/src/main/res/layout/header_drawer_account_selector.xml index b2cd275b2..71c4b2212 100644 --- a/twidere/src/main/res/layout/header_drawer_account_selector.xml +++ b/twidere/src/main/res/layout/header_drawer_account_selector.xml @@ -23,7 +23,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + tools:layout_height="160dp" + tools:layout_width="280dp"> + android:scaleType="centerCrop" + tools:src="@drawable/nyan_stars_background"/> + tools:src="@mipmap/ic_launcher"/> + android:orientation="horizontal" + android:baselineAligned="false"> + + \ No newline at end of file