added some eye candies :)
This commit is contained in:
parent
c05ba531c0
commit
e645cbbba5
|
@ -32,6 +32,7 @@ import com.commonsware.cwac.layouts.AspectLockedFrameLayout;
|
|||
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter;
|
||||
import org.mariotaku.twidere.graphic.LikeAnimationDrawable;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
import org.mariotaku.twidere.model.util.ParcelableMediaUtils;
|
||||
|
@ -91,11 +92,7 @@ public class StaggeredGridParcelableStatusesAdapter extends AbsParcelableStatuse
|
|||
final ParcelableMedia[] media = status.media;
|
||||
if (media == null || media.length < 1) return;
|
||||
final ParcelableMedia firstMedia = media[0];
|
||||
if (status.text_plain.codePointCount(0, status.text_plain.length()) == firstMedia.end) {
|
||||
mediaTextView.setText(status.text_unescaped.substring(0, firstMedia.start));
|
||||
} else {
|
||||
mediaTextView.setText(status.text_unescaped);
|
||||
}
|
||||
mediaTextView.setText(status.text_unescaped);
|
||||
aspectRatioSource.setSize(firstMedia.width, firstMedia.height);
|
||||
mediaImageContainer.setTag(firstMedia);
|
||||
mediaImageContainer.requestLayout();
|
||||
|
@ -153,6 +150,11 @@ public class StaggeredGridParcelableStatusesAdapter extends AbsParcelableStatuse
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playLikeAnimation(LikeAnimationDrawable.OnLikedListener listener) {
|
||||
|
||||
}
|
||||
|
||||
public void setOnClickListeners() {
|
||||
setStatusClickListener(adapter.getStatusClickListener());
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.mariotaku.twidere.adapter.AbsActivitiesAdapter;
|
|||
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration;
|
||||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag;
|
||||
import org.mariotaku.twidere.fragment.support.AbsStatusesFragment.DefaultOnLikedListener;
|
||||
import org.mariotaku.twidere.loader.iface.IExtendedLoader;
|
||||
import org.mariotaku.twidere.model.ParcelableActivity;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
|
@ -155,11 +156,11 @@ public abstract class AbsActivitiesFragment<Data> extends AbsContentListRecycler
|
|||
if (recyclerView == null || layoutManager == null) return false;
|
||||
final View focusedChild = RecyclerViewUtils.findRecyclerViewChild(recyclerView,
|
||||
layoutManager.getFocusedChild());
|
||||
int position = -1;
|
||||
int position = RecyclerView.NO_POSITION;
|
||||
if (focusedChild != null && focusedChild.getParent() == recyclerView) {
|
||||
position = recyclerView.getChildLayoutPosition(focusedChild);
|
||||
}
|
||||
if (position != -1) {
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
final ParcelableActivity activity = getAdapter().getActivity(position);
|
||||
if (activity == null) return false;
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
|
@ -188,7 +189,9 @@ public abstract class AbsActivitiesFragment<Data> extends AbsContentListRecycler
|
|||
if (status.is_favorite) {
|
||||
twitter.destroyFavoriteAsync(activity.account_id, status.id);
|
||||
} else {
|
||||
twitter.createFavoriteAsync(activity.account_id, status.id);
|
||||
final IStatusViewHolder holder = (IStatusViewHolder)
|
||||
recyclerView.findViewHolderForLayoutPosition(position);
|
||||
holder.playLikeAnimation(new DefaultOnLikedListener(twitter, status));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -362,7 +365,7 @@ public abstract class AbsActivitiesFragment<Data> extends AbsContentListRecycler
|
|||
if (status.is_favorite) {
|
||||
twitter.destroyFavoriteAsync(status.account_id, status.id);
|
||||
} else {
|
||||
twitter.createFavoriteAsync(status.account_id, status.id);
|
||||
holder.playLikeAnimation(new DefaultOnLikedListener(twitter, status));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.mariotaku.twidere.adapter.AbsStatusesAdapter;
|
|||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
|
||||
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter.StatusAdapterListener;
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag;
|
||||
import org.mariotaku.twidere.graphic.LikeAnimationDrawable;
|
||||
import org.mariotaku.twidere.loader.iface.IExtendedLoader;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
|
@ -184,7 +185,9 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentListRecyclerVi
|
|||
if (status.is_favorite) {
|
||||
twitter.destroyFavoriteAsync(status.account_id, status.id);
|
||||
} else {
|
||||
twitter.createFavoriteAsync(status.account_id, status.id);
|
||||
final IStatusViewHolder holder = (IStatusViewHolder)
|
||||
recyclerView.findViewHolderForLayoutPosition(position);
|
||||
holder.playLikeAnimation(new DefaultOnLikedListener(twitter, status));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -351,7 +354,7 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentListRecyclerVi
|
|||
if (status.is_favorite) {
|
||||
twitter.destroyFavoriteAsync(status.account_id, status.id);
|
||||
} else {
|
||||
twitter.createFavoriteAsync(status.account_id, status.id);
|
||||
holder.playLikeAnimation(new DefaultOnLikedListener(twitter, status));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -547,6 +550,23 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentListRecyclerVi
|
|||
return Utils.getReadPositionTagWithAccounts(getReadPositionTag(), getAccountIds());
|
||||
}
|
||||
|
||||
public static final class DefaultOnLikedListener implements LikeAnimationDrawable.OnLikedListener {
|
||||
private final ParcelableStatus mStatus;
|
||||
private final AsyncTwitterWrapper mTwitter;
|
||||
|
||||
public DefaultOnLikedListener(final AsyncTwitterWrapper twitter, final ParcelableStatus status) {
|
||||
mStatus = status;
|
||||
mTwitter = twitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLiked() {
|
||||
if (mStatus.is_favorite) return false;
|
||||
mTwitter.createFavoriteAsync(mStatus.account_id, mStatus.id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected final class StatusesBusCallback {
|
||||
|
||||
protected StatusesBusCallback() {
|
||||
|
|
|
@ -47,7 +47,10 @@ import android.support.v4.app.FragmentManagerAccessor;
|
|||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.ActionProvider;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.ActionMenuView;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.support.v7.widget.FixedLinearLayoutManager;
|
||||
|
@ -97,6 +100,7 @@ import org.mariotaku.twidere.api.twitter.model.TranslationResult;
|
|||
import org.mariotaku.twidere.constant.IntentConstants;
|
||||
import org.mariotaku.twidere.loader.support.ConversationLoader;
|
||||
import org.mariotaku.twidere.loader.support.ParcelableStatusLoader;
|
||||
import org.mariotaku.twidere.menu.support.FavoriteItemProvider;
|
||||
import org.mariotaku.twidere.model.ParcelableActivity;
|
||||
import org.mariotaku.twidere.model.ParcelableActivityCursorIndices;
|
||||
import org.mariotaku.twidere.model.ParcelableActivityValuesCreator;
|
||||
|
@ -1231,7 +1235,22 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
|||
final StatusFragment fragment = adapter.getFragment();
|
||||
final FragmentActivity activity = fragment.getActivity();
|
||||
final MenuInflater inflater = activity.getMenuInflater();
|
||||
inflater.inflate(R.menu.menu_detail_status, menuBar.getMenu());
|
||||
final Menu menu = menuBar.getMenu();
|
||||
inflater.inflate(R.menu.menu_detail_status, menu);
|
||||
final MenuItem favoriteItem = menu.findItem(R.id.favorite);
|
||||
final ActionProvider provider = MenuItemCompat.getActionProvider(favoriteItem);
|
||||
if (provider instanceof FavoriteItemProvider) {
|
||||
final int defaultColor = ThemeUtils.getActionIconColor(activity);
|
||||
final FavoriteItemProvider itemProvider = (FavoriteItemProvider) provider;
|
||||
itemProvider.setDefaultColor(defaultColor);
|
||||
final int favoriteHighlight = ContextCompat.getColor(activity, R.color.highlight_favorite);
|
||||
final int likeHighlight = ContextCompat.getColor(activity, R.color.highlight_like);
|
||||
final boolean useStar = adapter.shouldUseStarsForLikes();
|
||||
itemProvider.setActivatedColor(useStar ? favoriteHighlight : likeHighlight);
|
||||
itemProvider.setIcon(useStar ? R.drawable.ic_action_star : R.drawable.ic_action_heart);
|
||||
itemProvider.setUseStar(useStar);
|
||||
itemProvider.init(menuBar, favoriteItem);
|
||||
}
|
||||
ThemeUtils.wrapMenuIcon(menuBar, MENU_GROUP_STATUS_SHARE);
|
||||
mediaPreviewLoad.setOnClickListener(this);
|
||||
profileContainer.setOnClickListener(this);
|
||||
|
|
|
@ -0,0 +1,804 @@
|
|||
package org.mariotaku.twidere.graphic;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Property;
|
||||
import android.view.Gravity;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
|
||||
import org.mariotaku.twidere.graphic.iface.DoNotWrapDrawable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/11/4.
|
||||
*/
|
||||
public class LikeAnimationDrawable extends LayerDrawable implements Animatable, DoNotWrapDrawable {
|
||||
|
||||
private static final Property<IconLayer, Float> ICON_SCALE = new Property<IconLayer, Float>(Float.class, "icon_scale") {
|
||||
@Override
|
||||
public void set(IconLayer object, Float value) {
|
||||
object.setScale(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(IconLayer object) {
|
||||
return object.getScale();
|
||||
}
|
||||
};
|
||||
private static final Property<Layer, Float> LAYER_PROGRESS = new Property<Layer, Float>(Float.class, "layer_progress") {
|
||||
@Override
|
||||
public void set(Layer object, Float value) {
|
||||
object.setProgress(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(Layer object) {
|
||||
return object.getProgress();
|
||||
}
|
||||
};
|
||||
private final int mDefaultColor, mLikeColor;
|
||||
@Style
|
||||
private final int mStyle;
|
||||
private long mDuration = 500;
|
||||
|
||||
private AnimatorSet mCurrentAnimator;
|
||||
private WeakReference<OnLikedListener> mListenerRef;
|
||||
|
||||
public LikeAnimationDrawable(final Context context, final int likeIcon, final int defaultColor,
|
||||
final int likeColor, @Style final int style) {
|
||||
super(createLayers(context, likeIcon, defaultColor, style));
|
||||
mDefaultColor = defaultColor;
|
||||
mLikeColor = likeColor;
|
||||
mStyle = style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (mCurrentAnimator != null) return;
|
||||
|
||||
final AnimatorSet animatorSet = new AnimatorSet();
|
||||
|
||||
final AbsLayer particleLayer = getParticleShineLayer();
|
||||
final AbsLayer circleLayer = getCircleLayer();
|
||||
final IconLayer iconLayer = getIconLayer();
|
||||
|
||||
switch (mStyle) {
|
||||
case Style.LIKE: {
|
||||
setupLikeAnimation(animatorSet, particleLayer, circleLayer, iconLayer);
|
||||
break;
|
||||
}
|
||||
case Style.FAVORITE: {
|
||||
setupFavoriteAnimation(animatorSet, particleLayer, circleLayer, iconLayer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
resetState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
mCurrentAnimator = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mCurrentAnimator = null;
|
||||
if (mListenerRef == null) return;
|
||||
final OnLikedListener listener = mListenerRef.get();
|
||||
if (listener == null) return;
|
||||
if (!listener.onLiked()) {
|
||||
resetState();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetState() {
|
||||
iconLayer.setColorFilter(mDefaultColor, PorterDuff.Mode.SRC_ATOP);
|
||||
particleLayer.setProgress(-1);
|
||||
}
|
||||
});
|
||||
animatorSet.start();
|
||||
mCurrentAnimator = animatorSet;
|
||||
}
|
||||
|
||||
|
||||
private void setupFavoriteAnimation(final AnimatorSet animatorSet, final Layer particleLayer,
|
||||
final Layer circleLayer, final IconLayer iconLayer) {
|
||||
setupLikeAnimation(animatorSet, particleLayer, circleLayer, iconLayer);
|
||||
}
|
||||
|
||||
private void setupLikeAnimation(final AnimatorSet animatorSet, final Layer particleLayer,
|
||||
final Layer circleLayer, final IconLayer iconLayer) {
|
||||
final long scaleDownDuration = Math.round(1f / 24f * mDuration);
|
||||
final long ovalExpandDuration = Math.round(4f / 24f * mDuration);
|
||||
final long iconExpandOffset = Math.round(6f / 24f * mDuration);
|
||||
final long iconExpandDuration = Math.round(8f / 24f * mDuration);
|
||||
final long iconNormalDuration = Math.round(4f / 24f * mDuration);
|
||||
final long particleExpandDuration = Math.round(12f / 24f * mDuration);
|
||||
final long circleExplodeDuration = Math.round(5f / 24f * mDuration);
|
||||
|
||||
final ObjectAnimator iconScaleDown = ObjectAnimator.ofFloat(iconLayer, ICON_SCALE, 1, 0);
|
||||
iconScaleDown.setDuration(scaleDownDuration);
|
||||
iconScaleDown.setInterpolator(new AccelerateInterpolator(2));
|
||||
iconScaleDown.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
iconLayer.setColorFilter(mDefaultColor, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
final ObjectAnimator ovalExpand = ObjectAnimator.ofFloat(circleLayer, LAYER_PROGRESS, 0, 0.5f);
|
||||
ovalExpand.setDuration(ovalExpandDuration);
|
||||
|
||||
|
||||
final ObjectAnimator iconExpand = ObjectAnimator.ofFloat(iconLayer, ICON_SCALE, 0, 1.25f);
|
||||
iconExpand.setDuration(iconExpandDuration);
|
||||
iconExpand.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
iconLayer.setColorFilter(mLikeColor, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
final ObjectAnimator particleExplode = ObjectAnimator.ofFloat(particleLayer, LAYER_PROGRESS, 0, 0.5f);
|
||||
particleExplode.setDuration(iconExpandDuration);
|
||||
|
||||
final ObjectAnimator iconNormal = ObjectAnimator.ofFloat(iconLayer, ICON_SCALE, 1.25f, 1);
|
||||
iconNormal.setDuration(iconNormalDuration);
|
||||
final ObjectAnimator circleExplode = ObjectAnimator.ofFloat(circleLayer, LAYER_PROGRESS, 0.5f, 0.95f, 0.95f, 1);
|
||||
circleExplode.setDuration(circleExplodeDuration);
|
||||
circleExplode.setInterpolator(new DecelerateInterpolator());
|
||||
|
||||
|
||||
final ObjectAnimator particleFade = ObjectAnimator.ofFloat(particleLayer, LAYER_PROGRESS, 0.5f, 1);
|
||||
particleFade.setDuration(particleExpandDuration);
|
||||
|
||||
|
||||
animatorSet.play(iconScaleDown);
|
||||
animatorSet.play(ovalExpand).after(iconScaleDown);
|
||||
animatorSet.play(iconExpand).after(iconExpandOffset);
|
||||
animatorSet.play(particleExplode).after(iconExpandOffset);
|
||||
animatorSet.play(circleExplode).after(iconExpandOffset);
|
||||
|
||||
animatorSet.play(iconNormal).after(iconExpand);
|
||||
animatorSet.play(particleFade).after(iconExpand);
|
||||
}
|
||||
|
||||
private IconLayer getIconLayer() {
|
||||
return (IconLayer) getDrawable(2);
|
||||
}
|
||||
|
||||
private AbsLayer getCircleLayer() {
|
||||
return (AbsLayer) getDrawable(0);
|
||||
}
|
||||
|
||||
private AbsLayer getParticleShineLayer() {
|
||||
return (AbsLayer) getDrawable(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (mCurrentAnimator == null) return;
|
||||
mCurrentAnimator.cancel();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mCurrentAnimator != null && mCurrentAnimator.isRunning();
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
mDuration = duration;
|
||||
}
|
||||
|
||||
public void setOnLikedListener(OnLikedListener listener) {
|
||||
mListenerRef = new WeakReference<>(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
getIconLayer().setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
private static Drawable[] createLayers(final Context context, final int likeIcon, int defaultColor, int style) {
|
||||
final IconLayer iconDrawable = new IconLayer(ContextCompat.getDrawable(context, likeIcon));
|
||||
iconDrawable.setColorFilter(defaultColor, PorterDuff.Mode.SRC_ATOP);
|
||||
final AbsLayer particleLayer;
|
||||
final Palette palette;
|
||||
switch (style) {
|
||||
case Style.FAVORITE: {
|
||||
palette = new FavoritePalette();
|
||||
particleLayer = new ShineLayer(iconDrawable.getIntrinsicWidth(),
|
||||
iconDrawable.getIntrinsicHeight(), palette);
|
||||
break;
|
||||
}
|
||||
case Style.LIKE: {
|
||||
palette = new LikePalette();
|
||||
particleLayer = new ParticleLayer(iconDrawable.getIntrinsicWidth(),
|
||||
iconDrawable.getIntrinsicHeight(), palette);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
particleLayer.setProgress(-1);
|
||||
final Drawable circleLayer = new CircleLayer(iconDrawable.getIntrinsicWidth(), iconDrawable.getIntrinsicHeight(), palette);
|
||||
return new Drawable[]{circleLayer, particleLayer, iconDrawable};
|
||||
}
|
||||
|
||||
private interface Layer {
|
||||
|
||||
float getProgress();
|
||||
|
||||
void setProgress(float progress);
|
||||
}
|
||||
|
||||
public interface OnLikedListener {
|
||||
boolean onLiked();
|
||||
}
|
||||
|
||||
public interface Palette {
|
||||
int getParticleColor(int count, int index, float progress);
|
||||
|
||||
int getCircleColor(float progress);
|
||||
}
|
||||
|
||||
@IntDef({Style.LIKE, Style.FAVORITE})
|
||||
public @interface Style {
|
||||
int LIKE = 1;
|
||||
int FAVORITE = 2;
|
||||
}
|
||||
|
||||
private static class ShineLayer extends AbsLayer {
|
||||
|
||||
private static final int PARTICLES_PIVOTS_COUNT = 5;
|
||||
|
||||
private final Paint mPaint;
|
||||
private int mFullRadius;
|
||||
private float mLineWidth;
|
||||
|
||||
public ShineLayer(final int intrinsicWidth, final int intrinsicHeight, final Palette palette) {
|
||||
super(intrinsicWidth, intrinsicHeight, palette);
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
setProgress(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConstantState createConstantState(final int intrinsicWidth,
|
||||
final int intrinsicHeight,
|
||||
final Palette palette) {
|
||||
return new AbsLayerState() {
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return new ShineLayer(intrinsicWidth, intrinsicHeight, palette);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void calculateLineStartEnd(float[] startEnd, float progress) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
final float progress = getProgress();
|
||||
if (progress < 0) return;
|
||||
final int particleColor = palette.getParticleColor(0, 0, progress);
|
||||
final Rect bounds = getBounds();
|
||||
mPaint.setColor(particleColor);
|
||||
mPaint.setStrokeWidth(mLineWidth);
|
||||
final float[] startEnd = new float[2];
|
||||
mPaint.setAlpha(0xFF);
|
||||
if (progress < 0.25f) {
|
||||
calcPhase1(startEnd, progress);
|
||||
} else if (progress < 0.5f) {
|
||||
calcPhase2(startEnd, progress);
|
||||
} else if (progress < 0.75f) {
|
||||
calcPhase3(startEnd, progress);
|
||||
} else {
|
||||
calcPhase4(startEnd, progress);
|
||||
mPaint.setAlpha(Math.round(0xFF * (1 - (progress - 0.75f) * 4)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < PARTICLES_PIVOTS_COUNT; i++) {
|
||||
final double degree = 360.0 / PARTICLES_PIVOTS_COUNT * i;
|
||||
final double mainParticleAngle = Math.toRadians(degree + 18);
|
||||
final float startX = (float) (bounds.centerX() + startEnd[0] * Math.cos(mainParticleAngle));
|
||||
final float startY = (float) (bounds.centerY() + startEnd[0] * Math.sin(mainParticleAngle));
|
||||
final float stopX = (float) (bounds.centerX() + startEnd[1] * Math.cos(mainParticleAngle));
|
||||
final float stopY = (float) (bounds.centerY() + startEnd[1] * Math.sin(mainParticleAngle));
|
||||
if (startEnd[1] - startEnd[0] <= 0) {
|
||||
canvas.drawPoint(startX, startY, mPaint);
|
||||
} else {
|
||||
canvas.drawLine(startX, startY, stopX, stopY, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void calcPhase4(float[] startEnd, float progress) {
|
||||
calcPhase3(startEnd, 0.75f);
|
||||
}
|
||||
|
||||
private void calcPhase3(float[] startEnd, float progress) {
|
||||
calcPhase2(startEnd, 0.5f);
|
||||
final float length = (startEnd[1] - startEnd[0]) * (1 - (progress - 0.5f) * 4);
|
||||
startEnd[0] = startEnd[1] - length;
|
||||
}
|
||||
|
||||
private void calcPhase2(float[] startEnd, float progress) {
|
||||
calcPhase1(startEnd, 0.25f);
|
||||
final float length = startEnd[1] - startEnd[0];
|
||||
final float initialStart = startEnd[0];
|
||||
startEnd[0] = initialStart + mFullRadius / 3 * (progress - 0.25f) * 4;
|
||||
startEnd[1] = startEnd[0] + length;
|
||||
}
|
||||
|
||||
private void calcPhase1(float[] startEnd, float progress) {
|
||||
// Start point: 1/4 of icon radius
|
||||
startEnd[0] = mFullRadius / 3;
|
||||
startEnd[1] = startEnd[0] + (mFullRadius / 4 * progress * 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mFullRadius = Math.min(bounds.width(), bounds.height()) / 2;
|
||||
mLineWidth = mFullRadius / 10f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ParticleLayer extends AbsLayer {
|
||||
|
||||
private static final int PARTICLES_PIVOTS_COUNT = 7;
|
||||
private final Paint mPaint;
|
||||
private float mFullRadius;
|
||||
private float mParticleSize;
|
||||
|
||||
public ParticleLayer(final int intrinsicWidth, final int intrinsicHeight,
|
||||
final Palette palette) {
|
||||
super(intrinsicWidth, intrinsicHeight, palette);
|
||||
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
setProgress(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConstantState createConstantState(final int intrinsicWidth,
|
||||
final int intrinsicHeight,
|
||||
final Palette palette) {
|
||||
return new AbsLayerState() {
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return new ParticleLayer(intrinsicWidth, intrinsicHeight, palette);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(final Canvas canvas) {
|
||||
final float progress = getProgress();
|
||||
if (progress < 0) return;
|
||||
final Rect bounds = getBounds();
|
||||
final float expandSpinProgress = Math.min(0.5f, progress);
|
||||
final float currentRadius = mFullRadius + (mFullRadius * expandSpinProgress);
|
||||
final float distance = mParticleSize + (mParticleSize * progress);
|
||||
final float mainStrokeWidth, subStrokeWidth;
|
||||
if (progress < 0.5) {
|
||||
// Scale factor: [1, 0.5)
|
||||
mainStrokeWidth = mParticleSize * (1 - progress);
|
||||
// Scale factor: [1, 1.25)
|
||||
subStrokeWidth = mParticleSize * (1 + progress / 2);
|
||||
} else {
|
||||
mainStrokeWidth = mParticleSize * (1 - progress);
|
||||
subStrokeWidth = mParticleSize * 1.25f * (1 - (progress - 0.5f) * 2);
|
||||
}
|
||||
|
||||
for (int i = 0; i < PARTICLES_PIVOTS_COUNT; i++) {
|
||||
final double degree = 360.0 / PARTICLES_PIVOTS_COUNT * i;
|
||||
final int color = palette.getParticleColor(PARTICLES_PIVOTS_COUNT, i, progress);
|
||||
|
||||
final double mainParticleAngle = Math.toRadians(degree - 115);
|
||||
final float mainParticleX = (float) (bounds.centerX() + currentRadius * Math.cos(mainParticleAngle));
|
||||
final float mainParticleY = (float) (bounds.centerY() + currentRadius * Math.sin(mainParticleAngle));
|
||||
|
||||
mPaint.setColor(color);
|
||||
mPaint.setStrokeWidth(mainStrokeWidth);
|
||||
if (mainStrokeWidth > 0) {
|
||||
canvas.drawPoint(mainParticleX, mainParticleY, mPaint);
|
||||
}
|
||||
|
||||
|
||||
final double particleAngle = Math.toRadians(90.0 * -expandSpinProgress + degree + 15);
|
||||
final float subParticleX = (float) (mainParticleX + distance * Math.cos(particleAngle));
|
||||
final float subParticleY = (float) (mainParticleY + distance * Math.sin(particleAngle));
|
||||
mPaint.setAlpha(Math.round(255f * (1 - progress / 2f)));
|
||||
|
||||
mPaint.setStrokeWidth(subStrokeWidth);
|
||||
if (subStrokeWidth > 0) {
|
||||
canvas.drawPoint(subParticleX, subParticleY, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mFullRadius = Math.min(bounds.width(), bounds.height()) / 2;
|
||||
mParticleSize = mFullRadius / 4f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CircleLayer extends AbsLayer {
|
||||
private final Paint mPaint;
|
||||
|
||||
private int mFullRadius;
|
||||
|
||||
public CircleLayer(final int intrinsicWidth, final int intrinsicHeight,
|
||||
final Palette palette) {
|
||||
super(intrinsicWidth, intrinsicHeight, palette);
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConstantState createConstantState(final int intrinsicWidth,
|
||||
final int intrinsicHeight,
|
||||
final Palette palette) {
|
||||
return new AbsLayerState() {
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return new CircleLayer(intrinsicWidth, intrinsicHeight, palette);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(final Canvas canvas) {
|
||||
final float progress = getProgress();
|
||||
final Rect bounds = getBounds();
|
||||
final float radius;
|
||||
if (progress < 0.5f) {
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
final float sizeProgress = Math.min(1, progress * 2);
|
||||
radius = sizeProgress * mFullRadius;
|
||||
} else {
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
final float innerLeftRatio = 1 - (progress - 0.5f) * 2f;
|
||||
final float strokeWidth = mFullRadius * innerLeftRatio;
|
||||
mPaint.setStrokeWidth(strokeWidth);
|
||||
radius = mFullRadius - strokeWidth / 2;
|
||||
if (strokeWidth <= 0) return;
|
||||
}
|
||||
mPaint.setColor(palette.getCircleColor(progress));
|
||||
canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mFullRadius = Math.min(bounds.width(), bounds.height()) / 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static abstract class AbsLayer extends Drawable implements Layer {
|
||||
protected final int intrinsicWidth;
|
||||
protected final int intrinsicHeight;
|
||||
protected final Palette palette;
|
||||
private float mProgress;
|
||||
private ConstantState mState;
|
||||
|
||||
public AbsLayer(final int intrinsicWidth, final int intrinsicHeight, final Palette palette) {
|
||||
this.intrinsicWidth = intrinsicWidth;
|
||||
this.intrinsicHeight = intrinsicHeight;
|
||||
this.palette = palette;
|
||||
mState = createConstantState(intrinsicWidth, intrinsicHeight, palette);
|
||||
}
|
||||
|
||||
protected abstract ConstantState createConstantState(int intrinsicWidth, int intrinsicHeight, final Palette palette);
|
||||
|
||||
@Override
|
||||
public void setAlpha(final int alpha) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setProgress(float progress) {
|
||||
mProgress = progress;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(final ColorFilter colorFilter) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getIntrinsicHeight() {
|
||||
return intrinsicHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getIntrinsicWidth() {
|
||||
return intrinsicWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
static abstract class AbsLayerState extends ConstantState {
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class FavoritePalette implements Palette {
|
||||
|
||||
private final ArgbEvaluator evaluator = new ArgbEvaluator();
|
||||
|
||||
@Override
|
||||
public int getParticleColor(int count, int index, float progress) {
|
||||
return (Integer) evaluator.evaluate(progress, 0xFFFF7020, 0xFFFD9050);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCircleColor(float progress) {
|
||||
return (Integer) evaluator.evaluate(progress, 0xFFFF9C00, 0xFFFFB024);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LikePalette implements Palette {
|
||||
|
||||
private final ArgbEvaluator evaluator = new ArgbEvaluator();
|
||||
private final float[] hsv = new float[3];
|
||||
|
||||
@Override
|
||||
public int getParticleColor(int count, int index, float progress) {
|
||||
final double degree = 360.0 / count * index;
|
||||
hsv[0] = (float) degree;
|
||||
hsv[1] = 0.4f;
|
||||
hsv[2] = 1f;
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCircleColor(float progress) {
|
||||
return (Integer) evaluator.evaluate(progress, 0xFFDE4689, 0xFFCD8FF5);
|
||||
}
|
||||
}
|
||||
|
||||
static class IconLayer extends Drawable implements Callback {
|
||||
private final Drawable mDrawable;
|
||||
private final Rect mTmpRect = new Rect();
|
||||
private float mScale;
|
||||
private boolean mMutated;
|
||||
private ConstantState mState;
|
||||
|
||||
public IconLayer(Drawable drawable) {
|
||||
if (drawable == null) throw new NullPointerException();
|
||||
mState = new ScaleConstantState(drawable);
|
||||
mDrawable = drawable;
|
||||
drawable.setCallback(this);
|
||||
setScale(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drawable scaled by this ScaleDrawable.
|
||||
*/
|
||||
public Drawable getDrawable() {
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
// overrides from Drawable.Callback
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable who) {
|
||||
if (getCallback() != null) {
|
||||
getCallback().invalidateDrawable(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable who, Runnable what, long when) {
|
||||
if (getCallback() != null) {
|
||||
getCallback().scheduleDrawable(this, what, when);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable who, Runnable what) {
|
||||
if (getCallback() != null) {
|
||||
getCallback().unscheduleDrawable(this, what);
|
||||
}
|
||||
}
|
||||
|
||||
// overrides from Drawable
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mScale <= 0) return;
|
||||
mDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return super.getChangingConfigurations()
|
||||
| mDrawable.getChangingConfigurations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(Rect padding) {
|
||||
// XXX need to adjust padding!
|
||||
return mDrawable.getPadding(padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setVisible(boolean visible, boolean restart) {
|
||||
mDrawable.setVisible(visible, restart);
|
||||
return super.setVisible(visible, restart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mDrawable.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mDrawable.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return mDrawable.getOpacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return mDrawable.isStateful();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
boolean changed = mDrawable.setState(state);
|
||||
onBoundsChange(getBounds());
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onLevelChange(int level) {
|
||||
mDrawable.setLevel(level);
|
||||
onBoundsChange(getBounds());
|
||||
invalidateSelf();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
updateBounds(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mDrawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mDrawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable mutate() {
|
||||
if (!mMutated && super.mutate() == this) {
|
||||
mDrawable.mutate();
|
||||
mMutated = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return mScale;
|
||||
}
|
||||
|
||||
public void setScale(float scale) {
|
||||
mScale = scale;
|
||||
updateBounds(getBounds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
|
||||
static class ScaleConstantState extends ConstantState {
|
||||
|
||||
private final Drawable mIcon;
|
||||
|
||||
public ScaleConstantState(Drawable icon) {
|
||||
mIcon = icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return new IconLayer(mIcon.mutate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return mIcon.getChangingConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBounds(Rect bounds) {
|
||||
final Rect r = mTmpRect;
|
||||
final int w = Math.round(mDrawable.getIntrinsicWidth() * mScale);
|
||||
final int h = Math.round(mDrawable.getIntrinsicHeight() * mScale);
|
||||
Gravity.apply(Gravity.CENTER, w, h, bounds, r);
|
||||
|
||||
if (w > 0 && h > 0) {
|
||||
mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
|
||||
}
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package org.mariotaku.twidere.graphic.iface;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/18.
|
||||
*/
|
||||
public interface DoNotWrapDrawable {
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package org.mariotaku.twidere.menu.support;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.view.ActionProvider;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.ActionMenuView;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import org.mariotaku.twidere.graphic.LikeAnimationDrawable;
|
||||
import org.mariotaku.twidere.graphic.LikeAnimationDrawable.Style;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/18.
|
||||
*/
|
||||
public class FavoriteItemProvider extends ActionProvider {
|
||||
private int mDefaultColor, mActivatedColor;
|
||||
private boolean mUseStar;
|
||||
private int mIcon;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param context Context for accessing resources.
|
||||
*/
|
||||
public FavoriteItemProvider(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateActionView() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setUseStar(boolean useStar) {
|
||||
mUseStar = useStar;
|
||||
}
|
||||
|
||||
public void setDefaultColor(int defaultColor) {
|
||||
mDefaultColor = defaultColor;
|
||||
}
|
||||
|
||||
public void setActivatedColor(int activatedColor) {
|
||||
mActivatedColor = activatedColor;
|
||||
}
|
||||
|
||||
public void invokeItem(MenuItem item, LikeAnimationDrawable.OnLikedListener listener) {
|
||||
if (MenuItemCompat.getActionProvider(item) != this) throw new IllegalArgumentException();
|
||||
final Drawable icon = item.getIcon();
|
||||
if (icon instanceof LikeAnimationDrawable) {
|
||||
((LikeAnimationDrawable) icon).setOnLikedListener(listener);
|
||||
((LikeAnimationDrawable) icon).start();
|
||||
}
|
||||
}
|
||||
|
||||
public void setIcon(int icon) {
|
||||
mIcon = icon;
|
||||
}
|
||||
|
||||
public void init(final ActionMenuView menuBar, MenuItem item) {
|
||||
if (MenuItemCompat.getActionProvider(item) != this) throw new IllegalArgumentException();
|
||||
final LikeAnimationDrawable drawable = new LikeAnimationDrawable(getContext(), mIcon,
|
||||
mDefaultColor, mActivatedColor, mUseStar ? Style.FAVORITE : Style.LIKE);
|
||||
drawable.setCallback(new ViewCallback(menuBar));
|
||||
item.setIcon(drawable);
|
||||
}
|
||||
|
||||
private static class ViewCallback implements Drawable.Callback {
|
||||
private final WeakReference<View> mViewRef;
|
||||
|
||||
public ViewCallback(View view) {
|
||||
mViewRef = new WeakReference<>(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable who) {
|
||||
final View view = mViewRef.get();
|
||||
if (view == null) return;
|
||||
view.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable who, Runnable what, long when) {
|
||||
final View view = mViewRef.get();
|
||||
if (view == null) return;
|
||||
view.postDelayed(what, when);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable who, Runnable what) {
|
||||
final View view = mViewRef.get();
|
||||
if (view == null) return;
|
||||
view.post(what);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import android.content.SharedPreferences;
|
|||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.preference.Preference;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -31,7 +32,9 @@ import android.view.ViewGroup;
|
|||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.adapter.DummyStatusHolderAdapter;
|
||||
import org.mariotaku.twidere.graphic.LikeAnimationDrawable;
|
||||
import org.mariotaku.twidere.view.holder.StatusViewHolder;
|
||||
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder;
|
||||
|
||||
public class CardPreviewPreference extends Preference implements Constants, OnSharedPreferenceChangeListener {
|
||||
|
||||
|
@ -87,6 +90,19 @@ public class CardPreviewPreference extends Preference implements Constants, OnSh
|
|||
mCompactModeChanged = false;
|
||||
mHolder.setupViewOptions();
|
||||
mHolder.displaySampleStatus();
|
||||
mHolder.setStatusClickListener(new IStatusViewHolder.SimpleStatusClickListener() {
|
||||
@Override
|
||||
public void onItemActionClick(RecyclerView.ViewHolder holder, int id, int position) {
|
||||
if (id == R.id.favorite_count) {
|
||||
((StatusViewHolder) holder).playLikeAnimation(new LikeAnimationDrawable.OnLikedListener() {
|
||||
@Override
|
||||
public boolean onLiked() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
super.onBindView(view);
|
||||
}
|
||||
|
||||
|
|
|
@ -945,8 +945,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
|
|||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
mCreatingFavoriteIds.put(account_id, status_id);
|
||||
|
||||
|
||||
bus.post(new StatusListChangedEvent());
|
||||
}
|
||||
|
||||
|
@ -957,14 +955,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
|
|||
final ParcelableStatus status = result.getData();
|
||||
|
||||
// BEGIN HotMobi
|
||||
|
||||
final TweetEvent event = TweetEvent.create(getContext(), status, TimelineType.OTHER);
|
||||
event.setAction(TweetEvent.Action.FAVORITE);
|
||||
HotMobiLogger.getInstance(getContext()).log(account_id, event);
|
||||
|
||||
// END HotMobi
|
||||
|
||||
|
||||
bus.post(new FavoriteCreatedEvent(status));
|
||||
Utils.showOkMessage(mContext, R.string.status_favorited, false);
|
||||
} else {
|
||||
|
|
|
@ -67,6 +67,7 @@ import org.mariotaku.twidere.R;
|
|||
import org.mariotaku.twidere.activity.iface.IThemedActivity;
|
||||
import org.mariotaku.twidere.graphic.ActionBarColorDrawable;
|
||||
import org.mariotaku.twidere.graphic.ActionIconDrawable;
|
||||
import org.mariotaku.twidere.graphic.iface.DoNotWrapDrawable;
|
||||
import org.mariotaku.twidere.preference.ThemeBackgroundPreference;
|
||||
import org.mariotaku.twidere.util.menu.TwidereMenuInfo;
|
||||
import org.mariotaku.twidere.util.support.ViewSupport;
|
||||
|
@ -1005,15 +1006,23 @@ public class ThemeUtils implements Constants {
|
|||
}
|
||||
|
||||
public static void wrapMenuIcon(ActionMenuView view, int... excludeGroups) {
|
||||
final Resources resources = view.getResources();
|
||||
final int colorDark = resources.getColor(R.color.action_icon_dark);
|
||||
final int colorLight = resources.getColor(R.color.action_icon_light);
|
||||
final Context context = view.getContext();
|
||||
final int colorDark = ContextCompat.getColor(context, R.color.action_icon_dark);
|
||||
final int colorLight = ContextCompat.getColor(context, R.color.action_icon_light);
|
||||
wrapMenuIcon(view, colorDark, colorLight, excludeGroups);
|
||||
}
|
||||
|
||||
public static int getActionIconColor(Context context) {
|
||||
final int colorDark = ContextCompat.getColor(context, R.color.action_icon_dark);
|
||||
final int colorLight = ContextCompat.getColor(context, R.color.action_icon_light);
|
||||
final int itemBackgroundColor = ThemeUtils.getThemeBackgroundColor(context);
|
||||
return TwidereColorUtils.getContrastYIQ(itemBackgroundColor, colorDark, colorLight);
|
||||
}
|
||||
|
||||
public static void wrapMenuIcon(ActionMenuView view, int colorDark, int colorLight, int... excludeGroups) {
|
||||
final int itemBackgroundColor = ThemeUtils.getThemeBackgroundColor(view.getContext());
|
||||
final int popupItemBackgroundColor = ThemeUtils.getThemeBackgroundColor(view.getContext(), view.getPopupTheme());
|
||||
final Context context = view.getContext();
|
||||
final int itemBackgroundColor = ThemeUtils.getThemeBackgroundColor(context);
|
||||
final int popupItemBackgroundColor = ThemeUtils.getThemeBackgroundColor(context, view.getPopupTheme());
|
||||
final int itemColor = TwidereColorUtils.getContrastYIQ(itemBackgroundColor, colorDark, colorLight);
|
||||
final int popupItemColor = TwidereColorUtils.getContrastYIQ(popupItemBackgroundColor, colorDark, colorLight);
|
||||
final Menu menu = view.getMenu();
|
||||
|
@ -1034,7 +1043,7 @@ public class ThemeUtils implements Constants {
|
|||
public static void wrapMenuItemIcon(@NonNull MenuItem item, int itemColor, int... excludeGroups) {
|
||||
if (ArrayUtils.contains(excludeGroups, item.getGroupId())) return;
|
||||
final Drawable icon = item.getIcon();
|
||||
if (icon == null) return;
|
||||
if (icon == null || icon instanceof DoNotWrapDrawable) return;
|
||||
if (icon instanceof ActionIconDrawable) {
|
||||
((ActionIconDrawable) icon).setDefaultColor(itemColor);
|
||||
item.setIcon(icon);
|
||||
|
|
|
@ -235,12 +235,12 @@ public class ThemedLayoutInflaterFactory implements LayoutInflaterFactory {
|
|||
} else if (tintable instanceof EditText) {
|
||||
tintable.setSupportBackgroundTintList(ColorStateList.valueOf(backgroundTintColor));
|
||||
} else if (isColorTint) {
|
||||
final int[][] states = {{android.R.attr.state_selected}, {android.R.attr.state_focused},
|
||||
{android.R.attr.state_pressed}, {0}};
|
||||
final int[] colors = {accentColor, accentColor, accentColor, noTintColor};
|
||||
tintable.setSupportBackgroundTintList(new ColorStateList(states, colors));
|
||||
// final int[][] states = {{android.R.attr.state_selected}, {android.R.attr.state_focused},
|
||||
// {android.R.attr.state_pressed}, {0}};
|
||||
// final int[] colors = {accentColor, accentColor, accentColor, noTintColor};
|
||||
// tintable.setSupportBackgroundTintList(new ColorStateList(states, colors));
|
||||
} else {
|
||||
tintable.setSupportBackgroundTintList(ColorStateList.valueOf(accentColor));
|
||||
// tintable.setSupportBackgroundTintList(ColorStateList.valueOf(accentColor));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -138,6 +138,7 @@ import org.mariotaku.twidere.api.twitter.model.Relationship;
|
|||
import org.mariotaku.twidere.api.twitter.model.Status;
|
||||
import org.mariotaku.twidere.api.twitter.model.UserMentionEntity;
|
||||
import org.mariotaku.twidere.fragment.iface.IBaseFragment.SystemWindowsInsetsCallback;
|
||||
import org.mariotaku.twidere.fragment.support.AbsStatusesFragment.DefaultOnLikedListener;
|
||||
import org.mariotaku.twidere.fragment.support.AccountsManagerFragment;
|
||||
import org.mariotaku.twidere.fragment.support.AddStatusFilterDialogFragment;
|
||||
import org.mariotaku.twidere.fragment.support.DestroyStatusDialogFragment;
|
||||
|
@ -174,6 +175,7 @@ import org.mariotaku.twidere.fragment.support.UsersListFragment;
|
|||
import org.mariotaku.twidere.graphic.ActionIconDrawable;
|
||||
import org.mariotaku.twidere.graphic.PaddingDrawable;
|
||||
import org.mariotaku.twidere.menu.SupportStatusShareProvider;
|
||||
import org.mariotaku.twidere.menu.support.FavoriteItemProvider;
|
||||
import org.mariotaku.twidere.model.AccountPreferences;
|
||||
import org.mariotaku.twidere.model.ParcelableAccount;
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials;
|
||||
|
@ -215,7 +217,6 @@ import java.nio.charset.Charset;
|
|||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -2251,27 +2252,37 @@ public final class Utils implements Constants {
|
|||
}
|
||||
final MenuItem favorite = menu.findItem(R.id.favorite);
|
||||
if (favorite != null) {
|
||||
final boolean is_favorite;
|
||||
final boolean isFavorite;
|
||||
if (twitter.isCreatingFavorite(status.account_id, status.id)) {
|
||||
is_favorite = true;
|
||||
isFavorite = true;
|
||||
} else if (twitter.isDestroyingFavorite(status.account_id, status.id)) {
|
||||
is_favorite = false;
|
||||
isFavorite = false;
|
||||
} else {
|
||||
is_favorite = status.is_favorite;
|
||||
isFavorite = status.is_favorite;
|
||||
}
|
||||
if (preferences.getBoolean(KEY_I_WANT_MY_STARS_BACK)) {
|
||||
final Drawable oldIcon = favorite.getIcon();
|
||||
if (oldIcon instanceof ActionIconDrawable) {
|
||||
final Drawable starIcon = ContextCompat.getDrawable(context, R.drawable.ic_action_star);
|
||||
favorite.setIcon(new ActionIconDrawable(starIcon, ((ActionIconDrawable) oldIcon).getDefaultColor()));
|
||||
} else {
|
||||
favorite.setIcon(R.drawable.ic_action_star);
|
||||
}
|
||||
ActionIconDrawable.setMenuHighlight(favorite, new TwidereMenuInfo(is_favorite, favoriteHighlight));
|
||||
favorite.setTitle(is_favorite ? R.string.unfavorite : R.string.favorite);
|
||||
ActionProvider provider = MenuItemCompat.getActionProvider(favorite);
|
||||
final boolean useStar = preferences.getBoolean(KEY_I_WANT_MY_STARS_BACK);
|
||||
if (provider instanceof FavoriteItemProvider) {
|
||||
|
||||
|
||||
} else {
|
||||
ActionIconDrawable.setMenuHighlight(favorite, new TwidereMenuInfo(is_favorite, likeHighlight));
|
||||
favorite.setTitle(is_favorite ? R.string.undo_like : R.string.like);
|
||||
if (useStar) {
|
||||
final Drawable oldIcon = favorite.getIcon();
|
||||
if (oldIcon instanceof ActionIconDrawable) {
|
||||
final Drawable starIcon = ContextCompat.getDrawable(context, R.drawable.ic_action_star);
|
||||
favorite.setIcon(new ActionIconDrawable(starIcon, ((ActionIconDrawable) oldIcon).getDefaultColor()));
|
||||
} else {
|
||||
favorite.setIcon(R.drawable.ic_action_star);
|
||||
}
|
||||
ActionIconDrawable.setMenuHighlight(favorite, new TwidereMenuInfo(isFavorite, favoriteHighlight));
|
||||
} else {
|
||||
ActionIconDrawable.setMenuHighlight(favorite, new TwidereMenuInfo(isFavorite, likeHighlight));
|
||||
}
|
||||
}
|
||||
if (useStar) {
|
||||
favorite.setTitle(isFavorite ? R.string.unfavorite : R.string.favorite);
|
||||
} else {
|
||||
favorite.setTitle(isFavorite ? R.string.undo_like : R.string.like);
|
||||
}
|
||||
}
|
||||
final MenuItem translate = menu.findItem(R.id.translate);
|
||||
|
@ -2561,11 +2572,13 @@ public final class Utils implements Constants {
|
|||
return pm.getDrawable(info.packageName, info.metaData.getInt(key), info.applicationInfo);
|
||||
}
|
||||
|
||||
public static boolean handleMenuItemClick(@NonNull Context context, @Nullable Fragment fragment,
|
||||
@NonNull FragmentManager fm,
|
||||
@NonNull UserColorNameManager colorNameManager,
|
||||
@NonNull AsyncTwitterWrapper twitter,
|
||||
@NonNull ParcelableStatus status, @NonNull MenuItem item) {
|
||||
public static boolean handleMenuItemClick(@NonNull final Context context,
|
||||
@Nullable final Fragment fragment,
|
||||
@NonNull final FragmentManager fm,
|
||||
@NonNull final UserColorNameManager colorNameManager,
|
||||
@NonNull final AsyncTwitterWrapper twitter,
|
||||
@NonNull final ParcelableStatus status,
|
||||
@NonNull final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.copy: {
|
||||
if (ClipboardUtils.setText(context, status.text_plain)) {
|
||||
|
@ -2597,7 +2610,13 @@ public final class Utils implements Constants {
|
|||
if (status.is_favorite) {
|
||||
twitter.destroyFavoriteAsync(status.account_id, status.id);
|
||||
} else {
|
||||
twitter.createFavoriteAsync(status.account_id, status.id);
|
||||
ActionProvider provider = MenuItemCompat.getActionProvider(item);
|
||||
if (provider instanceof FavoriteItemProvider) {
|
||||
((FavoriteItemProvider) provider).invokeItem(item,
|
||||
new DefaultOnLikedListener(twitter, status));
|
||||
} else {
|
||||
twitter.createFavoriteAsync(status.account_id, status.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.mariotaku.twidere.view.holder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.text.BidiFormatter;
|
||||
|
@ -18,6 +19,7 @@ import org.apache.commons.lang3.ArrayUtils;
|
|||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter;
|
||||
import org.mariotaku.twidere.graphic.LikeAnimationDrawable;
|
||||
import org.mariotaku.twidere.model.ParcelableLocation;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
|
@ -401,15 +403,40 @@ public class StatusViewHolder extends ViewHolder implements Constants, IStatusVi
|
|||
nameView.setNameFirst(nameFirst);
|
||||
quotedNameView.setNameFirst(nameFirst);
|
||||
|
||||
final int likeIcon, likeStyle;
|
||||
if (adapter.shouldUseStarsForLikes()) {
|
||||
favoriteCountView.setActivatedColor(ContextCompat.getColor(adapter.getContext(),
|
||||
R.color.highlight_favorite));
|
||||
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(favoriteCountView,
|
||||
R.drawable.ic_action_star, 0, 0, 0);
|
||||
likeIcon = R.drawable.ic_action_star;
|
||||
likeStyle = LikeAnimationDrawable.Style.FAVORITE;
|
||||
} else {
|
||||
likeIcon = R.drawable.ic_action_heart;
|
||||
likeStyle = LikeAnimationDrawable.Style.LIKE;
|
||||
}
|
||||
final LikeAnimationDrawable drawable = new LikeAnimationDrawable(adapter.getContext(),
|
||||
likeIcon, favoriteCountView.getColor(), favoriteCountView.getActivatedColor(),
|
||||
likeStyle);
|
||||
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(favoriteCountView,
|
||||
drawable, null, null, null);
|
||||
drawable.setCallback(favoriteCountView);
|
||||
timeView.setShowAbsoluteTime(adapter.isShowAbsoluteTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playLikeAnimation(@NonNull LikeAnimationDrawable.OnLikedListener listener) {
|
||||
boolean handled = false;
|
||||
for (Drawable drawable : favoriteCountView.getCompoundDrawables()) {
|
||||
if (drawable instanceof LikeAnimationDrawable) {
|
||||
((LikeAnimationDrawable) drawable).setOnLikedListener(listener);
|
||||
((LikeAnimationDrawable) drawable).start();
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
listener.onLiked();
|
||||
}
|
||||
}
|
||||
|
||||
void displayExtraTypeIcon(String cardName, ParcelableMedia[] media, ParcelableLocation location, String placeFullName, boolean sensitive) {
|
||||
if (TwitterCardUtils.CARD_NAME_AUDIO.equals(cardName)) {
|
||||
extraTypeView.setImageResource(sensitive ? R.drawable.ic_action_warning : R.drawable.ic_action_music);
|
||||
|
|
|
@ -21,10 +21,12 @@ package org.mariotaku.twidere.view.holder.iface;
|
|||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.mariotaku.twidere.adapter.iface.ContentCardClickListener;
|
||||
import org.mariotaku.twidere.graphic.LikeAnimationDrawable;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
import org.mariotaku.twidere.view.CardMediaContainer;
|
||||
|
@ -51,6 +53,8 @@ public interface IStatusViewHolder extends CardMediaContainer.OnMediaClickListen
|
|||
|
||||
void setTextSize(float textSize);
|
||||
|
||||
void playLikeAnimation(LikeAnimationDrawable.OnLikedListener listener);
|
||||
|
||||
interface StatusClickListener extends ContentCardClickListener {
|
||||
|
||||
void onMediaClick(IStatusViewHolder holder, View view, ParcelableMedia media, int statusPosition);
|
||||
|
@ -61,4 +65,33 @@ public interface IStatusViewHolder extends CardMediaContainer.OnMediaClickListen
|
|||
|
||||
void onUserProfileClick(IStatusViewHolder holder, int position);
|
||||
}
|
||||
|
||||
abstract class SimpleStatusClickListener implements StatusClickListener {
|
||||
|
||||
public void onMediaClick(IStatusViewHolder holder, View view, ParcelableMedia media, int statusPosition) {
|
||||
|
||||
}
|
||||
|
||||
public void onStatusClick(IStatusViewHolder holder, int position) {
|
||||
|
||||
}
|
||||
|
||||
public boolean onStatusLongClick(IStatusViewHolder holder, int position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onUserProfileClick(IStatusViewHolder holder, int position) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemActionClick(RecyclerView.ViewHolder holder, int id, int position) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemMenuClick(RecyclerView.ViewHolder holder, View menuView, int position) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:clipChildren="false"
|
||||
android:focusable="true"
|
||||
android:paddingTop="@dimen/element_spacing_small"
|
||||
app:ignorePadding="true">
|
||||
|
@ -272,6 +273,7 @@
|
|||
android:layout_alignStart="@+id/profile_container"
|
||||
android:layout_below="@+id/status_content_space"
|
||||
android:layout_marginTop="@dimen/element_spacing_minus_mlarge"
|
||||
android:clipChildren="false"
|
||||
android:gravity="center_vertical|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
|
|
@ -396,6 +396,7 @@
|
|||
android:id="@+id/menu_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:actionBarSize"
|
||||
android:layout_below="@+id/counts_users_height_holder"/>
|
||||
android:layout_below="@+id/counts_users_height_holder"
|
||||
android:clipChildren="false"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
style="?actionButtonStyle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:addStatesFromChildren="true"
|
||||
android:background="?actionBarItemBackground"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/like"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_action_heart"/>
|
||||
</FrameLayout>
|
|
@ -18,6 +18,7 @@
|
|||
android:id="@id/favorite"
|
||||
android:icon="@drawable/ic_action_heart"
|
||||
android:title="@string/like"
|
||||
app:actionProviderClass="org.mariotaku.twidere.menu.support.FavoriteItemProvider"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@id/share"
|
||||
|
|
Loading…
Reference in New Issue