Improve GalleryDetailScene

This commit is contained in:
seven332 committed 2016-02-01 22:34:55 +08:00
1 parent 7b13da66af
commit 056a6eb394
59 files changed
+3451 -212

No files matched your search

@@ -17,29 +17,38 @@
package com.hippo.ehviewer;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import com.hippo.beerbelly.LruMap;
import com.hippo.conaco.Conaco;
import com.hippo.drawable.ImageWrapper;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.network.StatusCodeException;
import com.hippo.okhttp.CookieDB;
import com.hippo.scene.SceneApplication;
import com.hippo.utils.ReadableTime;
import com.hippo.yorozuya.IntIdGenerator;
import java.io.File;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
public class EhApplication extends Application {
public class EhApplication extends SceneApplication {
private IntIdGenerator mIdGenerator = new IntIdGenerator();
private SparseArray<Object> mGlobalStuffMap = new SparseArray<>();
private EhCookieStore mEhCookieStore;
private EhClient mEhClient;
private OkHttpClient mOkHttpClient;
private ImageWrapperHelper mImageWrapperHelper;
private Conaco<ImageWrapper> mConaco;
private LruMap<Integer, GalleryDetail> mGalleryDetailCache;
@Override
public void onCreate() {
@@ -49,6 +58,42 @@ public class EhApplication extends Application {
CookieDB.initialize(this);
StatusCodeException.initialize(this);
Settings.initialize(this);
ReadableTime.initialize(this);
}
public int putGlobalStuff(@NonNull Object o) {
int id = mIdGenerator.nextId();
mGlobalStuffMap.put(id, o);
return id;
}
public boolean containGlobalStuff(int id) {
return mGlobalStuffMap.indexOfKey(id) >= 0;
}
public Object getGlobalStuff(int id) {
return mGlobalStuffMap.get(id);
}
public Object removeGlobalStuff(int id) {
int index = mGlobalStuffMap.indexOfKey(id);
if (index >= 0) {
Object o = mGlobalStuffMap.valueAt(index);
mGlobalStuffMap.removeAt(index);
return o;
} else {
return null;
}
}
public boolean removeGlobalStuff(Object o) {
int index = mGlobalStuffMap.indexOfValue(o);
if (index >= 0) {
mGlobalStuffMap.removeAt(index);
return true;
} else {
return false;
}
}
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
@@ -115,4 +160,18 @@ public class EhApplication extends Application {
}
return application.mConaco;
}
@NonNull
public static LruMap<Integer, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mGalleryDetailCache == null) {
application.mGalleryDetailCache = new LruMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer lhs, Integer rhs) {
return lhs - rhs;
}
}, 3 * 60 * 1000); // 3 min timeout
}
return application.mGalleryDetailCache;
}
}
@@ -17,29 +17,48 @@
package com.hippo.ehviewer.ui.scene;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.PopupMenu;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RatingBar;
import android.widget.ScrollView;
import android.widget.TextView;
import com.hippo.easyrecyclerview.SimpleHolder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhCacheKeyFactory;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.data.GalleryTagGroup;
import com.hippo.ehviewer.client.parser.GalleryDetailParser;
import com.hippo.rippleold.RippleSalon;
import com.hippo.scene.SceneFragment;
import com.hippo.scene.StageActivity;
import com.hippo.text.Html;
import com.hippo.utils.ApiHelper;
import com.hippo.view.ViewTransition;
import com.hippo.utils.ReadableTime;
import com.hippo.vector.AnimatedVectorDrawable;
import com.hippo.vector.VectorDrawable;
import com.hippo.widget.AutoWrapLayout;
import com.hippo.widget.LoadImageView;
import com.hippo.widget.SimpleImageView;
// TODO Update drawer checked item
public class GalleryDetailScene extends BaseScene {
public class GalleryDetailScene extends BaseScene implements View.OnClickListener {
public final static String KEY_ACTION = "action";
public static final String ACTION_GALLERY_INFO = "action_gallery_info";
@@ -49,22 +68,13 @@ public class GalleryDetailScene extends BaseScene {
public static final String KEY_GID = "gid";
public static final String KEY_TOKEN = "token";
public static final int TYPE_HEADER = 0;
public static final int TYPE_BUTTON = 1;
public static final int TYPE_INFO = 2;
public static final int TYPE_ACTION = 3;
public static final int TYPE_TAG = 4;
public static final int TYPE_COMMENT = 5;
public static final int TYPE_PREVIEW = 6;
public static final int TYPE_PROGRESS = 7;
private static final String KEY_GALLERY_DETAIL = "gallery_detail";
private static final String KEY_REQUEST_ID = "request_id";
private EhClient mClient;
private RecyclerView mRecyclerView;
private View mMainProgressView;
private ScrollView mMainView;
private View mProgressView;
private ViewGroup mFailedView;
private TextView mFailedText;
private ViewTransition mViewTransition;
// Header
private View mHeader;
@@ -72,15 +82,51 @@ public class GalleryDetailScene extends BaseScene {
private TextView mTitle;
private TextView mUploader;
private TextView mCategory;
private SimpleImageView mOtherActions;
private ViewGroup mActionGroup;
private View mDownload;
private View mRead;
// Below header
private View mBelowHeader;
// Info
private View mInfo;
private TextView mLanguage;
private TextView mPages;
private TextView mSize;
private TextView mPosted;
private TextView mFavoredTimes;
// Actions
private View mActions;
private TextView mRatingText;
private RatingBar mRating;
private View mHeartGroup;
private TextView mHeart;
private TextView mHeartOutline;
private TextView mTorrent;
private TextView mShare;
private TextView mRate;
// Tags
private LinearLayout mTags;
private TextView mNoTags;
// Comments
private LinearLayout mComments;
private TextView mCommentsText;
// Progress
private View mProgress;
private AnimatedVectorDrawable mHeartDrawable;
private AnimatedVectorDrawable mHeartOutlineDrawable;
private PopupMenu mPopupMenu;
private String mAction;
private GalleryInfo mGalleryInfo;
private int mGid;
private String mKey;
private String mToken;
public GalleryDetailScene() {
}
private GalleryDetail mGalleryDetail;
private int mRequestId;
private void handleArgs(Bundle args) {
if (args == null) {
@@ -93,7 +139,39 @@ public class GalleryDetailScene extends BaseScene {
mGalleryInfo = args.getParcelable(KEY_GALLERY_INFO);
} else if (ACTION_GID_TOKEN.equals(action)) {
mGid = args.getInt(KEY_GID);
mKey = args.getString(KEY_TOKEN);
mToken = args.getString(KEY_TOKEN);
}
}
@Nullable
private String getGalleryDetailUrl() {
int gid;
String token;
if (mGalleryDetail != null) {
gid = mGalleryDetail.gid;
token = mGalleryDetail.token;
} else if (mGalleryInfo != null) {
gid = mGalleryInfo.gid;
token = mGalleryInfo.token;
} else if (ACTION_GID_TOKEN.equals(mAction)) {
gid = mGid;
token = mToken;
} else {
return null;
}
return EhUrl.getGalleryDetailUrl(gid, token);
}
// -1 for error
private int getGid() {
if (mGalleryDetail != null) {
return mGalleryDetail.gid;
} else if (mGalleryInfo != null) {
return mGalleryInfo.gid;
} else if (ACTION_GID_TOKEN.equals(mAction)) {
return mGid;
} else {
return -1;
}
}
@@ -101,39 +179,24 @@ public class GalleryDetailScene extends BaseScene {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mClient = EhApplication.getEhClient(getContext());
if (savedInstanceState == null) {
onInit();
} else {
onRestore(savedInstanceState);
}
}
private void onInit() {
handleArgs(getArguments());
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void bindView() {
int gid = -1;
boolean notFound = true;
if (ACTION_GALLERY_INFO.equals(mAction) && mGalleryInfo != null) {
GalleryInfo gi = mGalleryInfo;
gid = gi.gid;
mThumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb, true);
mTitle.setText(gi.title);
mUploader.setText(gi.uploader);
mCategory.setText(EhUtils.getCategory(gi.category));
mCategory.setTextColor(EhUtils.getCategoryColor(gi.category));
notFound = false;
} else if (ACTION_GID_TOKEN.equals(mAction)) {
gid = mGid;
// TODO
}
if (ApiHelper.SUPPORT_TRANSITION && gid != -1) {
mThumb.setTransitionName(TransitionNameFactory.getThumbTransitionName(gid));
mTitle.setTransitionName(TransitionNameFactory.getTitleTransitionName(gid));
mUploader.setTransitionName(TransitionNameFactory.getUploaderTransitionName(gid));
mCategory.setTransitionName(TransitionNameFactory.getCategoryTransitionName(gid));
}
if (notFound) {
// TODO Show not found error
}
private void onRestore(Bundle savedInstanceState) {
mAction = savedInstanceState.getString(KEY_ACTION);
mGalleryInfo = savedInstanceState.getParcelable(KEY_GALLERY_INFO);
mGid = savedInstanceState.getInt(KEY_GID);
mToken = savedInstanceState.getString(KEY_TOKEN);
mGalleryDetail = savedInstanceState.getParcelable(KEY_GALLERY_DETAIL);
mRequestId = savedInstanceState.getInt(KEY_REQUEST_ID);
}
@Nullable
@@ -143,67 +206,500 @@ public class GalleryDetailScene extends BaseScene {
View view = inflater.inflate(R.layout.scene_gallery_detail, container, false);
ViewGroup main = (ViewGroup) view.findViewById(R.id.main);
mRecyclerView = (RecyclerView) main.findViewById(R.id.recycler_view);
mMainProgressView = main.findViewById(R.id.progress_view);
mMainView = (ScrollView) main.findViewById(R.id.scroll_view);
mProgressView = main.findViewById(R.id.progress_view);
mFailedView = (ViewGroup) main.findViewById(R.id.tip);
mFailedText = (TextView) mFailedView.getChildAt(1);
mViewTransition = new ViewTransition(mRecyclerView, mMainProgressView, mFailedView);
mRecyclerView.setAdapter(new GalleryDetailAdapter());
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mHeader = inflater.inflate(R.layout.gallery_detail_header, mRecyclerView, false);
View mainView = mMainView;
mHeader = mainView.findViewById(R.id.header);
mThumb = (LoadImageView) mHeader.findViewById(R.id.thumb);
mTitle = (TextView) mHeader.findViewById(R.id.title);
mUploader = (TextView) mHeader.findViewById(R.id.uploader);
mCategory = (TextView) mHeader.findViewById(R.id.category);
mOtherActions = (SimpleImageView) mHeader.findViewById(R.id.other_actions);
mActionGroup = (ViewGroup) mHeader.findViewById(R.id.action_card);
mDownload = mActionGroup.findViewById(R.id.download);
mRead = mActionGroup.findViewById(R.id.read);
mOtherActions.setDrawable(VectorDrawable.create(getContext(), R.drawable.ic_dots_vertical));
RippleSalon.addRipple(mOtherActions, false);
RippleSalon.addRipple(mDownload, false);
RippleSalon.addRipple(mRead, false);
mOtherActions.setOnClickListener(this);
mDownload.setOnClickListener(this);
mRead.setOnClickListener(this);
bindView();
mBelowHeader = mainView.findViewById(R.id.below_header);
View belowHeader = mBelowHeader;
mInfo = belowHeader.findViewById(R.id.info);
mLanguage = (TextView) mInfo.findViewById(R.id.language);
mPages = (TextView) mInfo.findViewById(R.id.pages);
mSize = (TextView) mInfo.findViewById(R.id.size);
mPosted = (TextView) mInfo.findViewById(R.id.posted);
mFavoredTimes = (TextView) mInfo.findViewById(R.id.favoredTimes);
RippleSalon.addRipple(mInfo, false);
mInfo.setOnClickListener(this);
mActions = belowHeader.findViewById(R.id.actions);
mRatingText = (TextView) mActions.findViewById(R.id.rating_text);
mRating = (RatingBar) mActions.findViewById(R.id.rating);
mHeartGroup = mActions.findViewById(R.id.heart_group);
mHeart = (TextView) mHeartGroup.findViewById(R.id.heart);
mHeartOutline = (TextView) mHeartGroup.findViewById(R.id.heart_outline);
mTorrent = (TextView) mActions.findViewById(R.id.torrent);
mShare = (TextView) mActions.findViewById(R.id.share);
mRate = (TextView) mActions.findViewById(R.id.rate);
RippleSalon.addRipple(mHeartGroup, false);
RippleSalon.addRipple(mTorrent, false);
RippleSalon.addRipple(mShare, false);
RippleSalon.addRipple(mRate, false);
mHeartGroup.setOnClickListener(this);
mTorrent.setOnClickListener(this);
mShare.setOnClickListener(this);
mRate.setOnClickListener(this);
mTags = (LinearLayout) belowHeader.findViewById(R.id.tags);
mNoTags = (TextView) mTags.findViewById(R.id.no_tags);
mComments = (LinearLayout) belowHeader.findViewById(R.id.comments);
mCommentsText = (TextView) mComments.findViewById(R.id.comments_text);
mProgress = mainView.findViewById(R.id.progress);
fetchGalleryDetail();
adjustViewVisibility();
if (mGalleryDetail == null) {
bindViewFirst();
} else {
bindViewSecond();
}
setTransitionName();
return view;
}
private class GalleryDetailAdapter extends RecyclerView.Adapter<SimpleHolder> {
@Override
public void onDestroyView() {
super.onDestroyView();
@Override
public SimpleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE_HEADER:
return new SimpleHolder(mHeader);
/*
case TYPE_BUTTON:
return new SimpleHolder(mButton);
case TYPE_INFO:
return new SimpleHolder(mInfo);
case TYPE_ACTION:
return new SimpleHolder(mAction);
case TYPE_TAG:
return new SimpleHolder(mTag);
case TYPE_COMMENT:
return new SimpleHolder(mComment);
case TYPE_PREVIEW:
return new SimpleHolder(mPreview);
case TYPE_PROGRESS:
return new SimpleHolder(mProgress);
*/
default:
throw new IllegalStateException("Unknown type " + viewType);
mMainView = null;
mProgressView = null;
mFailedView = null;
mFailedText = null;
mHeader = null;
mThumb = null;
mTitle = null;
mUploader = null;
mCategory = null;
mOtherActions = null;
mActionGroup = null;
mDownload = null;
mRead = null;
mBelowHeader = null;
mInfo = null;
mLanguage = null;
mPages = null;
mSize = null;
mPosted = null;
mFavoredTimes = null;
mActions = null;
mRatingText = null;
mRating = null;
mHeartGroup = null;
mHeart = null;
mHeartOutline = null;
mTorrent = null;
mShare = null;
mRate = null;
mProgress = null;
mHeartDrawable = null;
mHeartOutlineDrawable = null;
mPopupMenu = null;
}
private void fetchGalleryDetail() {
if (mGalleryDetail != null) {
return;
}
int gid = getGid();
if (gid == -1) {
return;
}
// Get from cache
mGalleryDetail = EhApplication.getGalleryDetailCache(getContext()).get(gid);
if (mGalleryDetail != null) {
return;
}
EhApplication application = (EhApplication) getContext().getApplicationContext();
if (application.containGlobalStuff(mRequestId)) {
// request exist
return;
}
// Do request
String url = getGalleryDetailUrl();
if (url == null) {
return;
}
EhClient.Callback callback = new GetGalleryDetailListener(getContext(),
((StageActivity) getActivity()).getStageId(), getTag());
mRequestId = ((EhApplication) getContext().getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
.setArgs(url)
.setCallback(callback);
EhApplication.getEhClient(getContext()).execute(request);
}
private void adjustViewVisibility() {
if (mGalleryDetail != null) {
mMainView.setVisibility(View.VISIBLE);
mProgressView.setVisibility(View.GONE);
mFailedView.setVisibility(View.GONE);
mBelowHeader.setVisibility(View.VISIBLE);
mProgress.setVisibility(View.GONE);
} else if (mGalleryInfo != null) {
mMainView.setVisibility(View.VISIBLE);
mProgressView.setVisibility(View.GONE);
mFailedView.setVisibility(View.GONE);
mBelowHeader.setVisibility(View.GONE);
mProgress.setVisibility(View.VISIBLE);
} else {
mMainView.setVisibility(View.GONE);
if (TextUtils.isEmpty(mFailedText.getText())) {
mProgressView.setVisibility(View.VISIBLE);
mFailedView.setVisibility(View.GONE);
} else {
mProgressView.setVisibility(View.GONE);
mFailedView.setVisibility(View.VISIBLE);
}
}
}
private void bindViewFirst() {
if (mGalleryDetail != null) {
return;
}
if (ACTION_GALLERY_INFO.equals(mAction) && mGalleryInfo != null) {
GalleryInfo gi = mGalleryInfo;
mThumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb, true);
mTitle.setText(gi.title);
mUploader.setText(gi.uploader);
mCategory.setText(EhUtils.getCategory(gi.category));
mCategory.setTextColor(EhUtils.getCategoryColor(gi.category));
// Hide actions
mActionGroup.setVisibility(View.GONE);
}
}
private void ensureHeartDrawable() {
if (mHeartDrawable == null) {
mHeartDrawable = AnimatedVectorDrawable.create(getContext(), R.drawable.ic_heart_animated);
if (mHeartDrawable == null) {
throw new IllegalStateException("Can't parse heart drawable");
}
mHeartDrawable.setBounds(0, 0, mHeartDrawable.getIntrinsicWidth(),
mHeartDrawable.getIntrinsicHeight());
mHeart.setCompoundDrawables(null, mHeartDrawable, null, null);
}
if (mHeartOutlineDrawable == null) {
mHeartOutlineDrawable = AnimatedVectorDrawable.create(getContext(), R.drawable.ic_heart_outline_animated);
if (mHeartOutlineDrawable == null) {
throw new IllegalStateException("Can't parse heart drawable");
}
mHeartOutlineDrawable.setBounds(0, 0, mHeartOutlineDrawable.getIntrinsicWidth(),
mHeartOutlineDrawable.getIntrinsicHeight());
mHeartOutline.setCompoundDrawables(null, mHeartOutlineDrawable, null, null);
}
}
private void bindViewSecond() {
GalleryDetail gd = mGalleryDetail;
if (gd == null) {
return;
}
if (mActionGroup.getVisibility() == View.GONE) {
mActionGroup.setVisibility(View.VISIBLE);
}
Resources resources = getContext().getResources();
if (TextUtils.isEmpty(mCategory.getText())) {
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb, true);
mTitle.setText(gd.title);
mUploader.setText(gd.uploader);
mCategory.setText(EhUtils.getCategory(gd.category));
mCategory.setTextColor(EhUtils.getCategoryColor(gd.category));
}
mLanguage.setText(gd.language);
mPages.setText(resources.getQuantityString(
R.plurals.page_count, gd.pages, gd.pages));
mSize.setText(gd.size);
mPosted.setText(gd.posted);
mFavoredTimes.setText(resources.getString(R.string.favored_times, gd.favoredTimes));
mRatingText.setText(getAllRatingText(gd.rating, gd.ratedTimes));
mRating.setRating(gd.rating);
ensureHeartDrawable();
if (gd.isFavored) {
mHeart.setVisibility(View.VISIBLE);
mHeartOutline.setVisibility(View.GONE);
} else {
mHeart.setVisibility(View.GONE);
mHeartOutline.setVisibility(View.VISIBLE);
}
mTorrent.setText(resources.getString(R.string.torrent_count, gd.torrentCount));
bindTags(gd.tags);
bindComments(gd.comments);
}
private void bindTags(GalleryTagGroup[] tagGroups) {
mTags.removeViews(1, mTags.getChildCount() - 1);
if (tagGroups == null || tagGroups.length == 0) {
mNoTags.setVisibility(View.VISIBLE);
return;
} else {
mNoTags.setVisibility(View.GONE);
}
LayoutInflater inflater = getActivity().getLayoutInflater();
int colorTag = getResources().getColor(R.color.colorPrimary);
int colorName = getResources().getColor(R.color.purple_a400);
for (int i = 0, s = tagGroups.length; i < s; i++) {
GalleryTagGroup tg = tagGroups[i];
LinearLayout ll = (LinearLayout) inflater.inflate(R.layout.gallery_tag_group, mTags, false);
ll.setOrientation(LinearLayout.HORIZONTAL);
mTags.addView(ll);
TextView tgName = (TextView) inflater.inflate(R.layout.item_gallery_tag, ll, false);
ll.addView(tgName);
tgName.setText(tg.groupName);
tgName.setBackgroundColor(colorName);
AutoWrapLayout awl = new AutoWrapLayout(getContext());
ll.addView(awl, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
for (int j = 0, z = tg.size(); j < z; j++) {
TextView tag = (TextView) inflater.inflate(R.layout.item_gallery_tag, awl, false);
awl.addView(tag);
tag.setText(tg.getTagAt(j));
tag.setBackgroundColor(colorTag);
}
}
}
private void bindComments(GalleryComment[] comments) {
mComments.removeViews(0, mComments.getChildCount() - 1);
final int maxShowCount = 2;
if (comments == null || comments.length == 0) {
mCommentsText.setText(R.string.no_comments);
return;
} else if (comments.length <= maxShowCount) {
mCommentsText.setText(R.string.no_more_comments);
} else {
mCommentsText.setText(R.string.more_comment);
}
LayoutInflater inflater = getActivity().getLayoutInflater();
int length = Math.min(maxShowCount, comments.length);
for (int i = 0; i < length; i++) {
GalleryComment comment = comments[i];
View v = inflater.inflate(R.layout.item_gallery_comment, mComments, false);
mComments.addView(v, i);
((TextView) v.findViewById(R.id.user)).setText(comment.user);
((TextView) v.findViewById(R.id.time)).setText(ReadableTime.getTimeAgo(comment.time));
((TextView) v.findViewById(R.id.comment)).setText(Html.fromHtml(comment.comment));
}
}
private static String getRatingText(float rating, Resources resources) {
String undefine = "(´_ゝ`)";
if (Float.isNaN(rating)) {
return undefine;
}
int resId = 0;
switch (Math.round(rating * 2)) {
case 0:
resId = R.string.rating0; break;
case 1:
resId = R.string.rating1; break;
case 2:
resId = R.string.rating2; break;
case 3:
resId = R.string.rating3; break;
case 4:
resId = R.string.rating4; break;
case 5:
resId = R.string.rating5; break;
case 6:
resId = R.string.rating6; break;
case 7:
resId = R.string.rating7; break;
case 8:
resId = R.string.rating8; break;
case 9:
resId = R.string.rating9; break;
case 10:
resId = R.string.rating10; break;
}
if (resId == 0) {
return undefine;
} else {
return resources.getString(resId);
}
}
private String getAllRatingText(float rating, int ratedTimes) {
Resources resources = getResources();
return resources.getString(R.string.rating_text, getRatingText(rating, resources), ratedTimes);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setTransitionName() {
int gid = getGid();
if (gid != -1 && ApiHelper.SUPPORT_TRANSITION) {
mThumb.setTransitionName(TransitionNameFactory.getThumbTransitionName(gid));
mTitle.setTransitionName(TransitionNameFactory.getTitleTransitionName(gid));
mUploader.setTransitionName(TransitionNameFactory.getUploaderTransitionName(gid));
mCategory.setTransitionName(TransitionNameFactory.getCategoryTransitionName(gid));
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mAction != null) {
outState.putString(KEY_ACTION, mAction);
}
if (mGalleryInfo != null) {
outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo);
}
outState.putInt(KEY_GID, mGid);
if (mToken != null) {
outState.putString(KEY_TOKEN, mAction);
}
if (mGalleryDetail != null) {
outState.putParcelable(KEY_GALLERY_DETAIL, mGalleryDetail);
}
outState.putInt(KEY_REQUEST_ID, mRequestId);
}
private void ensurePopMenu() {
if (mPopupMenu != null) {
return;
}
PopupMenu popup = new PopupMenu(getContext(), mOtherActions);
mPopupMenu = popup;
popup.getMenuInflater().inflate(R.menu.gallery_detail_other_action, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_open_in_other_app:
// TODO
break;
}
return true;
}
});
}
@Override
public void onClick(View v) {
if (mOtherActions == v) {
ensurePopMenu();
mPopupMenu.show();
}
}
private void onGetGalleryDetailSuccess(GalleryDetailParser.Result result) {
mGalleryDetail = result.galleryDetail;
adjustViewVisibility();
bindViewSecond();
}
private void onGetGalleryDetailFailure(Exception e) {
}
private static class GetGalleryDetailListener implements EhClient.Callback<GalleryDetailParser.Result> {
private EhApplication mApplication;
private int mStageId;
private String mSceneTag;
public GetGalleryDetailListener(Context context, int stageId, String sceneTag) {
mApplication = (EhApplication) context.getApplicationContext();
mStageId = stageId;
mSceneTag = sceneTag;
}
private GalleryDetailScene getScene() {
StageActivity stage = mApplication.findStageActivityById(mStageId);
if (stage == null) {
return null;
}
SceneFragment scene = stage.findSceneByTag(mSceneTag);
if (scene instanceof GalleryDetailScene) {
return (GalleryDetailScene) scene;
} else {
return null;
}
}
@Override
public void onBindViewHolder(SimpleHolder holder, int position) {
public void onSuccess(GalleryDetailParser.Result result) {
mApplication.removeGlobalStuff(this);
// Put gallery detail to cache
GalleryDetail gd = result.galleryDetail;
EhApplication.getGalleryDetailCache(mApplication).put(gd.gid, gd);
// Notify success
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onGetGalleryDetailSuccess(result);
}
}
@Override
public int getItemCount() {
return 1;
public void onFailure(Exception e) {
mApplication.removeGlobalStuff(this);
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onGetGalleryDetailFailure(e);
}
}
@Override
public int getItemViewType(int position) {
return TYPE_HEADER; // TODO
public void onCancel() {
mApplication.removeGlobalStuff(this);
}
}
}
@@ -65,6 +65,9 @@ import com.hippo.ehviewer.widget.SearchBar;
import com.hippo.ehviewer.widget.SearchLayout;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.rippleold.RippleSalon;
import com.hippo.scene.SceneApplication;
import com.hippo.scene.SceneFragment;
import com.hippo.scene.StageActivity;
import com.hippo.scene.TransitionHelper;
import com.hippo.utils.ApiHelper;
import com.hippo.vector.VectorDrawable;
@@ -129,8 +132,6 @@ public final class GalleryListScene extends BaseScene
// Double click back exit
private long mPressBackTime = 0;
private EhRequest mRequest;
private Animator.AnimatorListener mFabAnimatorListener = null;
private boolean mHasFirstRefresh = false;
@@ -178,6 +179,8 @@ public final class GalleryListScene extends BaseScene
if (savedInstanceState == null) {
onInit();
} else {
onRestore(savedInstanceState);
}
}
@@ -186,6 +189,13 @@ public final class GalleryListScene extends BaseScene
handleArgs(getArguments());
}
@SuppressWarnings("WrongConstant")
private void onRestore(Bundle savedInstanceState) {
mHasFirstRefresh = savedInstanceState.getBoolean(KEY_HAS_FIRST_REFRESH);
mUrlBuilder = savedInstanceState.getParcelable(KEY_LIST_URL_BUILDER);
mState = savedInstanceState.getInt(KEY_STATE);
}
private void setSearchBarHint(Context context, SearchBar searchBar) {
Resources resources = context.getResources();
Drawable searchImage = VectorDrawable.create(context, R.drawable.ic_magnify);
@@ -235,22 +245,10 @@ public final class GalleryListScene extends BaseScene
setNavCheckedItem(checkedItemId);
}
private void restoreState(@Nullable Bundle savedInstanceState) {
if (savedInstanceState == null) {
return;
}
mHasFirstRefresh = savedInstanceState.getBoolean(KEY_HAS_FIRST_REFRESH);
mUrlBuilder = savedInstanceState.getParcelable(KEY_LIST_URL_BUILDER);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Restore url builder
restoreState(savedInstanceState);
View view = inflater.inflate(R.layout.scene_gallery_list, container, false);
Resources resources = getContext().getResources();
@@ -304,15 +302,15 @@ public final class GalleryListScene extends BaseScene
// Update list url builder
onUpdateUrlBuilder();
// Restore state
int newState = mState;
mState = STATE_NORMAL;
setState(newState, false);
// Only refresh for the first time
if (!mHasFirstRefresh) {
mHasFirstRefresh = true;
mHelper.firstRefresh();
} else {
// Restore state
int newState = mState;
mState = STATE_NORMAL;
setState(newState, false);
}
return view;
@@ -325,11 +323,6 @@ public final class GalleryListScene extends BaseScene
// End animation
mSearchBarMoveHelper.cancelAnimation();
// Cancel request
if (mRequest != null) {
mRequest.cancel();
}
mFitPaddingLayout = null;
mRecyclerView = null;
mSearchLayout = null;
@@ -342,7 +335,6 @@ public final class GalleryListScene extends BaseScene
mRightDrawable = null;
mSearchBarMoveHelper = null;
mFabAnimatorListener = null;
mRequest = null;
}
@Override
@@ -353,13 +345,6 @@ public final class GalleryListScene extends BaseScene
outState.putInt(KEY_STATE, mState);
}
@Override
@SuppressWarnings("WrongConstant")
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
setState(savedInstanceState.getInt(KEY_STATE), false);
}
@Override
public void onClick(View v) {
if (mFab == v) {
@@ -904,9 +889,9 @@ public final class GalleryListScene extends BaseScene
String url = mUrlBuilder.build();
EhRequest request = new EhRequest();
mRequest = request;
request.setMethod(EhClient.METHOD_GET_GALLERY_LIST);
request.setCallback(new GetGalleryListListener(taskId));
request.setCallback(new GetGalleryListListener(getContext(),
((StageActivity) getActivity()).getStageId(), getTag(), taskId));
request.setArgs(url);
mClient.execute(request);
}
@@ -932,38 +917,67 @@ public final class GalleryListScene extends BaseScene
}
}
private class GetGalleryListListener implements EhClient.Callback<GalleryListParser.Result> {
private void onGetGalleryListSuccess(GalleryListParser.Result result, int taskId) {
if (mHelper != null && mSearchBarMoveHelper != null &&
mHelper.isCurrentTask(taskId)) {
mHelper.setPages(taskId, result.pages);
mHelper.onGetPageData(taskId, result.galleryInfos);
mSearchBarMoveHelper.returnSearchBarPosition();
}
}
private void onGetGalleryListFailure(Exception e, int taskId) {
if (mHelper != null && mSearchBarMoveHelper != null &&
mHelper.isCurrentTask(taskId)) {
mHelper.onGetExpection(taskId, e);
mSearchBarMoveHelper.returnSearchBarPosition();
}
}
private static class GetGalleryListListener implements EhClient.Callback<GalleryListParser.Result> {
private SceneApplication mApplication;
private int mStageId;
private String mSceneTag;
private int mTaskId;
public GetGalleryListListener(int taskId) {
public GetGalleryListListener(Context context, int stageId, String sceneTag, int taskId) {
mApplication = (SceneApplication) context.getApplicationContext();
mStageId = stageId;
mSceneTag = sceneTag;
mTaskId = taskId;
}
private GalleryListScene getScene() {
StageActivity stage = mApplication.findStageActivityById(mStageId);
if (stage == null) {
return null;
}
SceneFragment scene = stage.findSceneByTag(mSceneTag);
if (scene instanceof GalleryListScene) {
return (GalleryListScene) scene;
} else {
return null;
}
}
@Override
public void onSuccess(GalleryListParser.Result result) {
if (mHelper != null && mHelper.isCurrentTask(mTaskId)) {
mRequest = null;
mHelper.setPages(mTaskId, result.pages);
mHelper.onGetPageData(mTaskId, result.galleryInfos);
mSearchBarMoveHelper.returnSearchBarPosition();
GalleryListScene scene = getScene();
if (scene != null) {
scene.onGetGalleryListSuccess(result, mTaskId);
}
}
@Override
public void onFailure(Exception e) {
if (mHelper != null && mHelper.isCurrentTask(mTaskId)) {
mRequest = null;
mHelper.onGetExpection(mTaskId, e);
mSearchBarMoveHelper.returnSearchBarPosition();
GalleryListScene scene = getScene();
if (scene != null) {
scene.onGetGalleryListFailure(e, mTaskId);
}
}
@Override
public void onCancel() {
if (mHelper != null && mHelper.isCurrentTask(mTaskId)) {
mRequest = null;
}
}
public void onCancel() {}
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2016 Hippo Seven
*
* 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 com.hippo.ehviewer.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.hippo.ehviewer.R;
public class DividerView extends View {
private Paint mPaint;
private Rect mRect;
private int mDividerWidth;
private int mDividerHeight;
public DividerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public DividerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DividerView);
int color = a.getColor(R.styleable.DividerView_dividerColor, Color.BLACK);
mDividerWidth = a.getDimensionPixelOffset(R.styleable.DividerView_dividerWidth, 0);
mDividerHeight = a.getDimensionPixelOffset(R.styleable.DividerView_dividerHeight, 0);
a.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(color);
mRect = new Rect();
}
@Override
protected int getSuggestedMinimumWidth() {
return Math.max((getPaddingLeft() + getPaddingRight() + mDividerWidth), super.getSuggestedMinimumWidth());
}
@Override
protected int getSuggestedMinimumHeight() {
return Math.max((getPaddingTop() + getPaddingBottom() + mDividerHeight), super.getSuggestedMinimumHeight());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect rect = mRect;
rect.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
if (!rect.isEmpty()) {
canvas.drawRect(rect, mPaint);
}
}
}
@@ -32,6 +32,15 @@ public class SceneApplication extends Application {
stage.onRegister(id);
}
void registerStageActivity(StageActivity stage, int id) {
if (mStageMap.indexOfKey(id) >= 0) {
throw new IllegalStateException("The id exists: " + id);
}
mStageMap.put(id, stage);
stage.onRegister(id);
}
void unregisterStageActivity(int id) {
int index = mStageMap.indexOfKey(id);
if (index >= 0) {
@@ -33,6 +33,7 @@ public abstract class StageActivity extends AppCompatActivity {
private static final String TAG = StageActivity.class.getSimpleName();
private static final String KEY_STAGE_ACTIVITY_STAGE_ID = "stage_activity_stage_id";
private static final String KEY_STAGE_ACTIVITY_SCENE_TAG_LIST = "stage_activity_scene_tag_list";
private static final String KEY_STAGE_ACTIVITY_NEXT_ID = "stage_activity_next_id";
@@ -47,7 +48,18 @@ public abstract class StageActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((SceneApplication) getApplicationContext()).registerStageActivity(this);
if (savedInstanceState != null) {
mStageId = savedInstanceState.getInt(KEY_STAGE_ACTIVITY_STAGE_ID, IntIdGenerator.INVALID_ID);
mSceneTagList = savedInstanceState.getStringArrayList(KEY_STAGE_ACTIVITY_SCENE_TAG_LIST);
mIdGenerator.setNextId(savedInstanceState.getInt(KEY_STAGE_ACTIVITY_NEXT_ID));
}
if (mStageId == IntIdGenerator.INVALID_ID) {
((SceneApplication) getApplicationContext()).registerStageActivity(this);
} else {
((SceneApplication) getApplicationContext()).registerStageActivity(this, mStageId);
}
}
@Override
@@ -61,7 +73,6 @@ public abstract class StageActivity extends AppCompatActivity {
}
protected void onUnregister() {
mStageId = IntIdGenerator.INVALID_ID;
}
public int getStageId() {
@@ -250,14 +261,8 @@ public abstract class StageActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_STAGE_ACTIVITY_STAGE_ID, mStageId);
outState.putStringArrayList(KEY_STAGE_ACTIVITY_SCENE_TAG_LIST, mSceneTagList);
outState.putInt(KEY_STAGE_ACTIVITY_NEXT_ID, mIdGenerator.nextId());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mSceneTagList = savedInstanceState.getStringArrayList(KEY_STAGE_ACTIVITY_SCENE_TAG_LIST);
mIdGenerator.setNextId(savedInstanceState.getInt(KEY_STAGE_ACTIVITY_NEXT_ID));
}
}
@@ -125,7 +125,7 @@ public class StageLayout extends FrameLayout {
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
ArrayList<View> disappearingChildren = mSuperDisappearingChildren;
if (mDoTrick && disappearingChildren != null && disappearingChildren.size() > 0) {
if (mDoTrick) {
if (child == mDumpView) {
boolean more = false;
for (int i = disappearingChildren.size() - 1; i >= 0; i--) {
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,178 @@
/*
* Copyright 2015 Hippo Seven
*
* 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 com.hippo.utils;
import android.content.Context;
import android.content.res.Resources;
import com.hippo.ehviewer.R;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public final class ReadableTime {
private static Resources sResources;
public static final long SECOND_MILLIS = 1000;
public static final long MINUTE_MILLIS = 60 * SECOND_MILLIS;
public static final long HOUR_MILLIS = 60 * MINUTE_MILLIS;
public static final long DAY_MILLIS = 24 * HOUR_MILLIS;
public static final long WEEK_MILLIS = 7 * DAY_MILLIS;
public static final long YEAR_MILLIS = 365 * DAY_MILLIS;
public static final int SIZE = 5;
public static final long[] MULTIPLES = {
YEAR_MILLIS,
DAY_MILLIS,
HOUR_MILLIS,
MINUTE_MILLIS,
SECOND_MILLIS
};
public static final int[] UNITS = {
R.plurals.year,
R.plurals.day,
R.plurals.hour,
R.plurals.minute,
R.plurals.second
};
private static final Calendar sCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+08:00"));
private static final Object sCalendarLock = new Object();
private static final SimpleDateFormat DATE_FORMAT_WITHOUT_YEAR =
new SimpleDateFormat("MMM d", Locale.getDefault());
private static final SimpleDateFormat DATE_FORMAT_WIT_YEAR =
new SimpleDateFormat("yyy MMM d", Locale.getDefault());
private static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("yy-MM-dd HH:mm", Locale.getDefault());
private static final Object sDateFormatLock1 = new Object();
private static final SimpleDateFormat FILENAMABLE_DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.getDefault());
private static final Object sDateFormatLock2 = new Object();
static {
// The website use GMT+08:00, so tell user the same
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
DATE_FORMAT_WITHOUT_YEAR.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
}
public static void initialize(Context context) {
sResources = context.getApplicationContext().getResources();
}
/*
public static String getDisplayTime(long time) {
if (Settings.getPrettyTime()) {
return getTimeAgo(time);
} else {
return getPlainTime(time);
}
}
*/
public static String getPlainTime(long time) {
synchronized (sDateFormatLock1) {
return DATE_FORMAT.format(new Date(time));
}
}
public static String getTimeAgo(long time) {
Resources resources = sResources;
long now = System.currentTimeMillis();
if (time > now + (2 * MINUTE_MILLIS) || time <= 0) {
return resources.getString(R.string.from_the_future);
}
final long diff = now - time;
if (diff < MINUTE_MILLIS) {
return resources.getString(R.string.just_now);
} else if (diff < 2 * MINUTE_MILLIS) {
return resources.getQuantityString(R.plurals.some_minutes_ago, 1, 1);
} else if (diff < 50 * MINUTE_MILLIS) {
int minutes = (int) (diff / MINUTE_MILLIS);
return resources.getQuantityString(R.plurals.some_minutes_ago, minutes, minutes);
} else if (diff < 90 * MINUTE_MILLIS) {
return resources.getQuantityString(R.plurals.some_hours_ago, 1, 1);
} else if (diff < 24 * HOUR_MILLIS) {
int hours = (int) (diff / HOUR_MILLIS);
return resources.getQuantityString(R.plurals.some_hours_ago, hours, hours);
} else if (diff < 48 * HOUR_MILLIS) {
return resources.getString(R.string.yesterday);
} else if (diff < WEEK_MILLIS) {
int days = (int) (diff / DAY_MILLIS);
return resources.getString(R.string.some_days_ago, days);
} else {
synchronized (sCalendarLock) {
Date nowDate = new Date(now);
Date timeDate = new Date(time);
sCalendar.setTime(nowDate);
int nowYear = sCalendar.get(Calendar.YEAR);
sCalendar.setTime(timeDate);
int timeYear = sCalendar.get(Calendar.YEAR);
if (nowYear == timeYear) {
return DATE_FORMAT_WITHOUT_YEAR.format(timeDate);
} else {
return DATE_FORMAT_WIT_YEAR.format(timeDate);
}
}
}
}
public static String getTimeInterval(long time) {
StringBuilder sb = new StringBuilder();
Resources resources = sResources;
long leftover = time;
boolean start = false;
for (int i = 0; i < SIZE; i++) {
long multiple = MULTIPLES[i];
long quotient = leftover / multiple;
long remainder = leftover % multiple;
if (start || quotient != 0 || i == SIZE - 1) {
if (start) {
sb.append(" ");
}
sb.append(quotient)
.append(" ")
.append(resources.getQuantityString(UNITS[i], (int) quotient));
start = true;
}
leftover = remainder;
}
return sb.toString();
}
public static String getFilenamableTime(long time) {
synchronized (sDateFormatLock2) {
return FILENAMABLE_DATE_FORMAT.format(new Date(time));
}
}
}
@@ -0,0 +1,268 @@
/*
* Copyright 2015 Hippo Seven
*
* 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 com.hippo.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.hippo.ehviewer.R;
import java.util.ArrayList;
import java.util.List;
/**
* A ViewGroup that can layout views in line and auto wrap
*
* @author Hippo
*
*/
public class AutoWrapLayout extends ViewGroup {
private final List<Rect> rectList = new ArrayList<>();
private Alignment mAlignment;
private static final Alignment[] sBaseLineArray = { Alignment.TOP,
Alignment.CENTER, Alignment.BOTTOM };
public enum Alignment {
TOP(0),
CENTER(1),
BOTTOM(2);
Alignment(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
public AutoWrapLayout(Context context) {
super(context);
}
public AutoWrapLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoWrapLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.AutoWrapLayout, defStyle, 0);
int index = a.getInt(R.styleable.AutoWrapLayout_alignment, -1);
if (index >= 0) {
setAlignment(sBaseLineArray[index]);
}
a.recycle();
}
public void setAlignment(Alignment baseLine) {
if (baseLine == null) {
return;
}
if (mAlignment != baseLine) {
mAlignment = baseLine;
requestLayout();
invalidate();
}
}
public Alignment getAlignment() {
return mAlignment;
}
private void adjustBaseLine(int lineHeight, int startIndex, int endIndex) {
if (mAlignment == Alignment.TOP)
return;
for (int index = startIndex; index < endIndex; index++) {
final View child = getChildAt(index);
final MarginLayoutParams lp =
(MarginLayoutParams)child.getLayoutParams();
Rect rect = rectList.get(index);
int offsetRaw = lineHeight - rect.height() - lp.topMargin - lp.bottomMargin;
if (mAlignment == Alignment.CENTER)
rect.offset(0, offsetRaw/2);
else if (mAlignment == Alignment.BOTTOM)
rect.offset(0, offsetRaw);
}
}
// TODO Take vertical mode
/**
* each row or line at least show one child
*
* horizontal only show child can show or partly show in parent
*/
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED)
maxWidth = Integer.MAX_VALUE;
if (heightMode == MeasureSpec.UNSPECIFIED)
maxHeight = Integer.MAX_VALUE;
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int maxRightBound = maxWidth - paddingRight;
int maxBottomBound = maxHeight - paddingBottom;
int left;
int top;
int right;
int bottom;
int rightBound = paddingLeft;
int maxRightNoPadding = rightBound;
int bottomBound;
int lastMaxBottom = paddingTop;
int maxBottom = lastMaxBottom;
int childWidth;
int childHeight;
int lineStartIndex = 0;
int lineEndIndex; // endIndex + 1
rectList.clear();
int childCount = getChildCount();
for (int index = 0; index < childCount; index++) {
final View child = getChildAt(index);
child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (child.getVisibility() == View.GONE)
continue;
final MarginLayoutParams lp =
(MarginLayoutParams)child.getLayoutParams();
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
left = rightBound + lp.leftMargin;
right = left + childWidth;
rightBound = right + lp.rightMargin;
if (rightBound > maxRightBound) { // Go to next row
lineEndIndex = index;
// Adjust child position base on baseline
adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, lineEndIndex);
// If child can't show in parent begin this line
if (maxBottom >= maxBottomBound)
break;
// If it is first item in line, try to show it all
if (lineEndIndex == lineStartIndex) {
child.measure(MeasureSpec.makeMeasureSpec(
maxWidth - paddingLeft - paddingRight
- lp.leftMargin - lp.rightMargin, MeasureSpec.AT_MOST),
MeasureSpec.UNSPECIFIED);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
}
left = paddingLeft + lp.leftMargin;
right = left + childWidth;
rightBound = right + lp.rightMargin;
lastMaxBottom = maxBottom;
top = lastMaxBottom + lp.topMargin;
bottom = top + childHeight;
bottomBound = bottom + lp.bottomMargin;
lineStartIndex = index;
} else {
top = lastMaxBottom + lp.topMargin;
bottom = top + childHeight;
bottomBound = bottom + lp.bottomMargin;
}
// Update max
if (rightBound > maxRightNoPadding)
maxRightNoPadding = rightBound;
if (bottomBound > maxBottom)
maxBottom = bottomBound;
Rect rect = new Rect();
rect.left = left;
rect.top = top;
rect.right = right;
rect.bottom = bottom;
rectList.add(rect);
}
// Handle last line baseline
adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, rectList.size());
int measuredWidth;
int measuredHeight;
if (widthMode == MeasureSpec.EXACTLY)
measuredWidth = maxWidth;
else
measuredWidth = maxRightNoPadding + paddingRight;
if (heightMode == MeasureSpec.EXACTLY)
measuredHeight = maxHeight;
else {
measuredHeight = maxBottom + paddingBottom;
if (heightMode == MeasureSpec.AT_MOST)
measuredHeight = measuredHeight > maxHeight ? maxHeight : measuredHeight;
}
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = rectList.size();
for(int i = 0; i < count; i++){
final View child = this.getChildAt(i);
if (child.getVisibility() == View.GONE)
continue;
Rect rect = rectList.get(i);
child.layout(rect.left, rect.top, rect.right, rect.bottom);
}
}
@Override
public MarginLayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected MarginLayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected MarginLayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new MarginLayoutParams(p);
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2015 Hippo Seven
*
* 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 com.hippo.widget;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.Layout;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.TextView;
public class LinkifyTextView extends TextView {
private ClickableSpan mCurrentSpan;
public LinkifyTextView(Context context) {
super(context);
}
public LinkifyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LinkifyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ClickableSpan getCurrentSpan() {
return mCurrentSpan;
}
public void clearCurrentSpan() {
mCurrentSpan = null;
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
// Let the parent or grandparent of TextView to handles click aciton.
// Otherwise click effect like ripple will not work, and if touch area
// do not contain a url, the TextView will still get MotionEvent.
// onTouchEven must be called with MotionEvent.ACTION_DOWN for each touch
// action on it, so we analyze touched url here.
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mCurrentSpan = null;
if (getText() instanceof Spanned) {
// Get this code from android.text.method.LinkMovementMethod.
// Work fine !
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] spans = ((Spanned)getText()).getSpans(off, off, ClickableSpan.class);
if (spans.length > 0) {
mCurrentSpan = spans[0];
}
}
}
return super.onTouchEvent(event);
}
}