优化书源结构

pull/5/head
fengyuecanzhu 4 years ago
parent 8296d4a506
commit 29cb92d7ee
  1. 1
      app/src/main/java/xyz/fycz/myreader/model/third2/analyzeRule/AnalyzeRule.java
  2. 20
      app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java
  3. 119
      app/src/main/java/xyz/fycz/myreader/webapi/BookApi.java
  4. 11
      app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java
  5. 18
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/BaseReadCrawler.java
  6. 4
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/BaseSourceCrawler.java
  7. 9
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/BookInfoCrawler.java
  8. 253
      app/src/main/java/xyz/fycz/myreader/widget/scroller/FastScrollRecyclerView.java
  9. 14
      app/src/main/java/xyz/fycz/myreader/widget/scroller/FastScrollStateChangeListener.java
  10. 531
      app/src/main/java/xyz/fycz/myreader/widget/scroller/FastScroller.java
  11. 39
      app/src/main/res/drawable/fastscroll_bubble.xml
  12. 31
      app/src/main/res/drawable/fastscroll_handle.xml
  13. 27
      app/src/main/res/drawable/fastscroll_track.xml
  14. 7
      app/src/main/res/layout/activity_replace_rule.xml
  15. 19
      app/src/main/res/layout/fragment_catalog.xml
  16. 13
      app/src/main/res/layout/fragment_import_source.xml
  17. 7
      app/src/main/res/layout/fragment_local_source.xml
  18. 50
      app/src/main/res/layout/view_fastscroller.xml
  19. 10
      app/src/main/res/values/attrs.xml
  20. 16
      app/src/main/res/values/dimens.xml
  21. 3
      app/src/main/res/values/ids.xml

@ -535,6 +535,7 @@ public class AnalyzeRule implements JsExtensions {
bindings.put("java", this);
bindings.put("result", result);
bindings.put("baseUrl", baseUrl);
bindings.put("book", book);
return SCRIPT_ENGINE.eval(jsStr, bindings);
}

@ -21,12 +21,14 @@ import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.entity.StrResponse;
import xyz.fycz.myreader.greendao.DbManager;
import xyz.fycz.myreader.greendao.entity.CookieBean;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeUrl;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -287,6 +289,24 @@ public class OkHttpUtils {
});
}
public static Map<String, String> getCookies(BookSource bookSource) {
if (bookSource != null) {
return getCookies(bookSource.getSourceUrl());
}
return new HashMap<>();
}
public static Map<String, String> getCookies(String url) {
Map<String, String> cookieMap = new HashMap<>();
if (url != null) {
CookieBean cookie = DbManager.getDaoSession().getCookieBeanDao().load(url);
if (cookie != null) {
cookieMap.put("Cookie", cookie.getCookie());
}
}
return cookieMap;
}
private static class Web {
private String content;
private String js = "document.documentElement.outerHTML";

@ -1,13 +1,21 @@
package xyz.fycz.myreader.webapi;
import org.jetbrains.annotations.NotNull;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.functions.Function;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.entity.StrResponse;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.model.mulvalmap.ConMVMap;
@ -18,49 +26,10 @@ import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.webapi.crawler.read.TianLaiReadCrawler;
import xyz.fycz.myreader.webapi.crawler.source.ThirdCrawler;
public class BookApi {
/**
* 获取章节列表
*
* @param book
*/
public static Observable<List<Chapter>> getBookChapters(Book book, final ReadCrawler rc) {
if (rc instanceof ThirdCrawler) {
return ThirdSourceApi.getBookChaptersByTC(book, (ThirdCrawler) rc);
}
String url = book.getChapterUrl();
String charset = rc.getCharset();
url = NetworkUtils.getAbsoluteURL(rc.getNameSpace(), url);
String finalUrl = url;
return Observable.create(emitter -> {
emitter.onNext(rc.getChaptersFromHtml(OkHttpUtils.getHtml(finalUrl, charset, rc.getHeaders())));
emitter.onComplete();
});
}
/**
* 获取章节正文
*
*/
public static Observable<String> getChapterContent(Chapter chapter, Book book, final ReadCrawler rc) {
if (rc instanceof ThirdCrawler) {
return ThirdSourceApi.getChapterContentByTC(chapter, book, (ThirdCrawler) rc);
}
String url = chapter.getUrl();
String charset = rc.getCharset();
url = NetworkUtils.getAbsoluteURL(rc.getNameSpace(), url);
String finalUrl = url;
return Observable.create(emitter -> {
emitter.onNext(rc.getContentFormHtml(OkHttpUtils.getHtml(finalUrl, charset, rc.getHeaders())));
emitter.onComplete();
});
}
import static xyz.fycz.myreader.util.utils.OkHttpUtils.getCookies;
public class BookApi {
/**
* 搜索小说
*
@ -85,8 +54,9 @@ public class BookApi {
}
String finalCharset = charset;
String finalKey = key;
return Observable.create(emitter -> {
try {
return Observable.create((ObservableOnSubscribe<StrResponse>) emitter -> {
Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace()));
if (rc.isPost()) {
String url = rc.getSearchLink();
String[] urlInfo = url.split(",");
@ -94,16 +64,12 @@ public class BookApi {
String body = makeSearchUrl(urlInfo[1], finalKey);
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody requestBody = RequestBody.create(mediaType, body);
emitter.onNext(rc.getBooksFromSearchHtml(OkHttpUtils.getHtml(url, requestBody, finalCharset, rc.getHeaders())));
emitter.onNext(OkHttpUtils.getStrResponse(url, requestBody, finalCharset, headers));
} else {
emitter.onNext(rc.getBooksFromSearchHtml(OkHttpUtils.getHtml(makeSearchUrl(rc.getSearchLink(), finalKey), finalCharset, rc.getHeaders())));
}
} catch (Exception e) {
e.printStackTrace();
emitter.onError(e);
emitter.onNext(OkHttpUtils.getStrResponse(makeSearchUrl(rc.getSearchLink(), finalKey), finalCharset, headers));
}
emitter.onComplete();
});
}).flatMap(response -> OkHttpUtils.setCookie(response, rc.getNameSpace())).flatMap(rc::getBooksFromStrResponse);
}
public static String makeSearchUrl(String url, String key) {
@ -111,7 +77,6 @@ public class BookApi {
}
/**
* 获取小说详细信息
*
@ -125,10 +90,54 @@ public class BookApi {
url = book.getInfoUrl();
url = NetworkUtils.getAbsoluteURL(bic.getNameSpace(), url);
String finalUrl = url;
return Observable.create(emitter -> {
emitter.onNext(bic.getBookInfo(OkHttpUtils.getHtml(finalUrl, bic.getCharset(),
((ReadCrawler) bic).getHeaders()), book));
return Observable.create((ObservableOnSubscribe<StrResponse>) emitter -> {
Map<String, String> headers = ((ReadCrawler) bic).getHeaders();
headers.putAll(getCookies(bic.getNameSpace()));
emitter.onNext(OkHttpUtils.getStrResponse(finalUrl, bic.getCharset(), headers));
emitter.onComplete();
});
}).flatMap(response -> OkHttpUtils.setCookie(response, bic.getNameSpace())).flatMap(response -> bic.getBookInfo(response, book));
}
/**
* 获取章节列表
*
* @param book
*/
public static Observable<List<Chapter>> getBookChapters(Book book, final ReadCrawler rc) {
if (rc instanceof ThirdCrawler) {
return ThirdSourceApi.getBookChaptersByTC(book, (ThirdCrawler) rc);
}
String url = book.getChapterUrl();
String charset = rc.getCharset();
url = NetworkUtils.getAbsoluteURL(rc.getNameSpace(), url);
String finalUrl = url;
return Observable.create((ObservableOnSubscribe<StrResponse>) emitter -> {
Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace()));
emitter.onNext(OkHttpUtils.getStrResponse(finalUrl, charset, headers));
emitter.onComplete();
}).flatMap(response -> OkHttpUtils.setCookie(response, rc.getNameSpace())).flatMap(rc::getChaptersFromStrResponse);
}
/**
* 获取章节正文
*/
public static Observable<String> getChapterContent(Chapter chapter, Book book, final ReadCrawler rc) {
if (rc instanceof ThirdCrawler) {
return ThirdSourceApi.getChapterContentByTC(chapter, book, (ThirdCrawler) rc);
}
String url = chapter.getUrl();
String charset = rc.getCharset();
url = NetworkUtils.getAbsoluteURL(rc.getNameSpace(), url);
String finalUrl = url;
return Observable.create((ObservableOnSubscribe<StrResponse>) emitter -> {
Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace()));
emitter.onNext(OkHttpUtils.getStrResponse(finalUrl, charset, headers));
emitter.onComplete();
}).flatMap(response -> OkHttpUtils.setCookie(response, rc.getNameSpace())).flatMap(rc::getContentFormStrResponse);
}
}

@ -25,6 +25,7 @@ import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.webapi.crawler.source.ThirdCrawler;
import static xyz.fycz.myreader.common.APPCONST.JS_PATTERN;
import static xyz.fycz.myreader.util.utils.OkHttpUtils.getCookies;
/**
* @author fengyue
@ -130,14 +131,4 @@ public class ThirdSourceApi {
}
}
private static Map<String, String> getCookies(BookSource bookSource){
Map<String, String> cookieMap = new HashMap<>();
if (bookSource != null){
CookieBean cookie = DbManager.getDaoSession().getCookieBeanDao().load(bookSource.getSourceUrl());
if (cookie != null) {
cookieMap.put("Cookie", cookie.getCookie());
}
}
return cookieMap;
}
}

@ -1,6 +1,7 @@
package xyz.fycz.myreader.webapi.crawler.base;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -19,7 +20,7 @@ import xyz.fycz.myreader.model.mulvalmap.ConMVMap;
public abstract class BaseReadCrawler implements ReadCrawler {
@Override
public Map<String, String> getHeaders() {
return null;
return new HashMap<>();
}
@Override
@ -39,16 +40,25 @@ public abstract class BaseReadCrawler implements ReadCrawler {
@Override
public Observable<ConMVMap<SearchBookBean, Book>> getBooksFromStrResponse(StrResponse response) {
return null;
return Observable.create(emitter -> {
emitter.onNext(getBooksFromSearchHtml(response.body()));
emitter.onComplete();
});
}
@Override
public Observable<List<Chapter>> getChaptersFromStrResponse(StrResponse response) {
return null;
return Observable.create(emitter -> {
emitter.onNext(getChaptersFromHtml(response.body()));
emitter.onComplete();
});
}
@Override
public Observable<String> getContentFormStrResponse(StrResponse response) {
return null;
return Observable.create(emitter -> {
emitter.onNext(getContentFormHtml(response.body()));
emitter.onComplete();
});
}
}

@ -348,7 +348,7 @@ public abstract class BaseSourceCrawler extends BaseReadCrawler implements BookI
@Override
public Map<String, String> getHeaders() {
if (!StringUtils.isJsonObject(source.getSourceHeaders()))
return null;
return super.getHeaders();
try {
JSONObject headersJson = new JSONObject(source.getSourceHeaders());
Map<String, String> headers = new HashMap<>();
@ -362,7 +362,7 @@ public abstract class BaseSourceCrawler extends BaseReadCrawler implements BookI
} catch (JSONException e) {
e.printStackTrace();
}
return null;
return super.getHeaders();
}
/**

@ -1,5 +1,8 @@
package xyz.fycz.myreader.webapi.crawler.base;
import io.reactivex.Observable;
import xyz.fycz.myreader.entity.StrResponse;
import xyz.fycz.myreader.greendao.entity.Book;
/**
@ -10,4 +13,10 @@ public interface BookInfoCrawler {
String getNameSpace();
String getCharset();
Book getBookInfo(String html, Book book);
default Observable<Book> getBookInfo(StrResponse strResponse, Book book){
return Observable.create(emitter -> {
emitter.onNext(getBookInfo(strResponse.body(), book));
emitter.onComplete();
});
}
}

@ -0,0 +1,253 @@
package xyz.fycz.myreader.widget.scroller;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.annotation.ColorInt;
import androidx.recyclerview.widget.RecyclerView;
import xyz.fycz.myreader.R;
@SuppressWarnings("unused")
public class FastScrollRecyclerView extends RecyclerView {
private FastScroller mFastScroller;
public FastScrollRecyclerView(Context context) {
super(context);
layout(context, null);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
public FastScrollRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FastScrollRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
layout(context, attrs);
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
if (adapter instanceof FastScroller.SectionIndexer) {
setSectionIndexer((FastScroller.SectionIndexer) adapter);
} else if (adapter == null) {
setSectionIndexer(null);
}
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
mFastScroller.setVisibility(visibility);
}
/**
* Set the {@link FastScroller.SectionIndexer} for the {@link FastScroller}.
*
* @param sectionIndexer The SectionIndexer that provides section text for the FastScroller
*/
public void setSectionIndexer(FastScroller.SectionIndexer sectionIndexer) {
mFastScroller.setSectionIndexer(sectionIndexer);
}
/**
* Set the enabled state of fast scrolling.
*
* @param enabled True to enable fast scrolling, false otherwise
*/
public void setFastScrollEnabled(boolean enabled) {
mFastScroller.setEnabled(enabled);
}
/**
* Hide the scrollbar when not scrolling.
*
* @param hideScrollbar True to hide the scrollbar, false to show
*/
public void setHideScrollbar(boolean hideScrollbar) {
mFastScroller.setFadeScrollbar(hideScrollbar);
}
/**
* Display a scroll track while scrolling.
*
* @param visible True to show scroll track, false to hide
*/
public void setTrackVisible(boolean visible) {
mFastScroller.setTrackVisible(visible);
}
/**
* Set the color of the scroll track.
*
* @param color The color for the scroll track
*/
public void setTrackColor(@ColorInt int color) {
mFastScroller.setTrackColor(color);
}
/**
* Set the color for the scroll handle.
*
* @param color The color for the scroll handle
*/
public void setHandleColor(@ColorInt int color) {
mFastScroller.setHandleColor(color);
}
/**
* Show the section bubble while scrolling.
*
* @param visible True to show the bubble, false to hide
*/
public void setBubbleVisible(boolean visible) {
mFastScroller.setBubbleVisible(visible);
}
/**
* Set the background color of the index bubble.
*
* @param color The background color for the index bubble
*/
public void setBubbleColor(@ColorInt int color) {
mFastScroller.setBubbleColor(color);
}
/**
* Set the text color of the index bubble.
*
* @param color The text color for the index bubble
*/
public void setBubbleTextColor(@ColorInt int color) {
mFastScroller.setBubbleTextColor(color);
}
/**
* Set the fast scroll state change listener.
*
* @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events
*/
public void setFastScrollStateChangeListener(FastScrollStateChangeListener fastScrollStateChangeListener) {
mFastScroller.setFastScrollStateChangeListener(fastScrollStateChangeListener);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFastScroller.attachRecyclerView(this);
ViewParent parent = getParent();
if (parent instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) parent;
viewGroup.addView(mFastScroller);
mFastScroller.setLayoutParams(viewGroup);
}
}
@Override
protected void onDetachedFromWindow() {
mFastScroller.detachRecyclerView();
super.onDetachedFromWindow();
}
private void layout(Context context, AttributeSet attrs) {
mFastScroller = new FastScroller(context, attrs);
mFastScroller.setId(R.id.fast_scroller);
}
}

@ -0,0 +1,14 @@
package xyz.fycz.myreader.widget.scroller;
public interface FastScrollStateChangeListener {
/**
* Called when fast scrolling begins
*/
void onFastScrollStart(FastScroller fastScroller);
/**
* Called when fast scrolling ends
*/
void onFastScrollStop(FastScroller fastScroller);
}

@ -0,0 +1,531 @@
package xyz.fycz.myreader.widget.scroller;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.util.utils.ColorUtil;
public class FastScroller extends LinearLayout {
private static final int sBubbleAnimDuration = 100;
private static final int sScrollbarAnimDuration = 300;
private static final int sScrollbarHideDelay = 1000;
private static final int sTrackSnapRange = 5;
@ColorInt
private int mBubbleColor;
@ColorInt
private int mHandleColor;
private int mBubbleHeight;
private int mHandleHeight;
private int mViewHeight;
private boolean mFadeScrollbar;
private boolean mShowBubble;
private SectionIndexer mSectionIndexer;
private ViewPropertyAnimator mScrollbarAnimator;
private ViewPropertyAnimator mBubbleAnimator;
private RecyclerView mRecyclerView;
private TextView mBubbleView;
private ImageView mHandleView;
private ImageView mTrackView;
private View mScrollbar;
private Drawable mBubbleImage;
private Drawable mHandleImage;
private Drawable mTrackImage;
private FastScrollStateChangeListener mFastScrollStateChangeListener;
private Runnable mScrollbarHider = this::hideScrollbar;
private RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (!mHandleView.isSelected() && isEnabled()) {
setViewPositions(getScrollProportion(recyclerView));
}
}
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (isEnabled()) {
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:
getHandler().removeCallbacks(mScrollbarHider);
cancelAnimation(mScrollbarAnimator);
if (!isViewVisible(mScrollbar)) {
showScrollbar();
}
break;
case RecyclerView.SCROLL_STATE_IDLE:
if (mFadeScrollbar && !mHandleView.isSelected()) {
getHandler().postDelayed(mScrollbarHider, sScrollbarHideDelay);
}
break;
}
}
}
};
public FastScroller(Context context) {
super(context);
layout(context, null);
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
}
public FastScroller(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
layout(context, attrs);
setLayoutParams(generateLayoutParams(attrs));
}
@Override
public void setLayoutParams(@NonNull ViewGroup.LayoutParams params) {
params.width = LayoutParams.WRAP_CONTENT;
super.setLayoutParams(params);
}
public void setLayoutParams(@NonNull ViewGroup viewGroup) {
@IdRes int recyclerViewId = mRecyclerView != null ? mRecyclerView.getId() : NO_ID;
int marginTop = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_top);
int marginBottom = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_bottom);
if (recyclerViewId == NO_ID) {
throw new IllegalArgumentException("RecyclerView must have a view ID");
}
if (viewGroup instanceof ConstraintLayout) {
ConstraintSet constraintSet = new ConstraintSet();
@IdRes int layoutId = getId();
constraintSet.clone((ConstraintLayout) viewGroup);
constraintSet.connect(layoutId, ConstraintSet.TOP, recyclerViewId, ConstraintSet.TOP);
constraintSet.connect(layoutId, ConstraintSet.BOTTOM, recyclerViewId, ConstraintSet.BOTTOM);
constraintSet.connect(layoutId, ConstraintSet.END, recyclerViewId, ConstraintSet.END);
constraintSet.applyTo((ConstraintLayout) viewGroup);
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) getLayoutParams();
layoutParams.setMargins(0, marginTop, 0, marginBottom);
setLayoutParams(layoutParams);
} else if (viewGroup instanceof CoordinatorLayout) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) getLayoutParams();
layoutParams.setAnchorId(recyclerViewId);
layoutParams.anchorGravity = GravityCompat.END;
layoutParams.setMargins(0, marginTop, 0, marginBottom);
setLayoutParams(layoutParams);
} else if (viewGroup instanceof FrameLayout) {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
layoutParams.gravity = GravityCompat.END;
layoutParams.setMargins(0, marginTop, 0, marginBottom);
setLayoutParams(layoutParams);
} else if (viewGroup instanceof RelativeLayout) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
int endRule = RelativeLayout.ALIGN_END;
layoutParams.addRule(RelativeLayout.ALIGN_TOP, recyclerViewId);
layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, recyclerViewId);
layoutParams.addRule(endRule, recyclerViewId);
layoutParams.setMargins(0, marginTop, 0, marginBottom);
setLayoutParams(layoutParams);
} else {
throw new IllegalArgumentException("Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout");
}
updateViewHeights();
}
public void setSectionIndexer(SectionIndexer sectionIndexer) {
mSectionIndexer = sectionIndexer;
}
public void attachRecyclerView(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
mRecyclerView.addOnScrollListener(mScrollListener);
post(() -> {
// set initial positions for bubble and handle
setViewPositions(getScrollProportion(mRecyclerView));
});
}
}
public void detachRecyclerView() {
if (mRecyclerView != null) {
mRecyclerView.removeOnScrollListener(mScrollListener);
mRecyclerView = null;
}
}
/**
* Hide the scrollbar when not scrolling.
*
* @param fadeScrollbar True to hide the scrollbar, false to show
*/
public void setFadeScrollbar(boolean fadeScrollbar) {
mFadeScrollbar = fadeScrollbar;
mScrollbar.setVisibility(fadeScrollbar ? GONE : VISIBLE);
}
/**
* Show the section bubble while scrolling.
*
* @param visible True to show the bubble, false to hide
*/
public void setBubbleVisible(boolean visible) {
mShowBubble = visible;
}
/**
* Display a scroll track while scrolling.
*
* @param visible True to show scroll track, false to hide
*/
public void setTrackVisible(boolean visible) {
mTrackView.setVisibility(visible ? VISIBLE : GONE);
}
/**
* Set the color of the scroll track.
*
* @param color The color for the scroll track
*/
public void setTrackColor(@ColorInt int color) {
@ColorInt int trackColor = color;
if (mTrackImage == null) {
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.fastscroll_track);
if (drawable != null) {
mTrackImage = DrawableCompat.wrap(drawable);
}
}
DrawableCompat.setTint(mTrackImage, trackColor);
mTrackView.setImageDrawable(mTrackImage);
}
/**
* Set the color for the scroll handle.
*
* @param color The color for the scroll handle
*/
public void setHandleColor(@ColorInt int color) {
mHandleColor = color;
if (mHandleImage == null) {
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.fastscroll_handle);
if (drawable != null) {
mHandleImage = DrawableCompat.wrap(drawable);
}
}
DrawableCompat.setTint(mHandleImage, mHandleColor);
mHandleView.setImageDrawable(mHandleImage);
}
/**
* Set the background color of the index bubble.
*
* @param color The background color for the index bubble
*/
public void setBubbleColor(@ColorInt int color) {
mBubbleColor = color;
if (mBubbleImage == null) {
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.fastscroll_bubble);
if (drawable != null) {
mBubbleImage = DrawableCompat.wrap(drawable);
}
}
DrawableCompat.setTint(mBubbleImage, mBubbleColor);
mBubbleView.setBackground(mBubbleImage);
}
/**
* Set the text color of the index bubble.
*
* @param color The text color for the index bubble
*/
public void setBubbleTextColor(@ColorInt int color) {
mBubbleView.setTextColor(color);
}
/**
* Set the fast scroll state change listener.
*
* @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events
*/
public void setFastScrollStateChangeListener(FastScrollStateChangeListener fastScrollStateChangeListener) {
mFastScrollStateChangeListener = fastScrollStateChangeListener;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setVisibility(enabled ? VISIBLE : GONE);
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (event.getX() < mHandleView.getX() - ViewCompat.getPaddingStart(mHandleView)) {
return false;
}
requestDisallowInterceptTouchEvent(true);
setHandleSelected(true);
getHandler().removeCallbacks(mScrollbarHider);
cancelAnimation(mScrollbarAnimator);
cancelAnimation(mBubbleAnimator);
if (!isViewVisible(mScrollbar)) {
showScrollbar();
}
if (mShowBubble && mSectionIndexer != null) {
showBubble();
}
if (mFastScrollStateChangeListener != null) {
mFastScrollStateChangeListener.onFastScrollStart(this);
}
case MotionEvent.ACTION_MOVE:
final float y = event.getY();
setViewPositions(y);
setRecyclerViewPosition(y);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
requestDisallowInterceptTouchEvent(false);
setHandleSelected(false);
if (mFadeScrollbar) {
getHandler().postDelayed(mScrollbarHider, sScrollbarHideDelay);
}
hideBubble();
if (mFastScrollStateChangeListener != null) {
mFastScrollStateChangeListener.onFastScrollStop(this);
}
return true;
}
return super.onTouchEvent(event);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewHeight = h;
}
private void setRecyclerViewPosition(float y) {
if (mRecyclerView != null && mRecyclerView.getAdapter() != null) {
int itemCount = mRecyclerView.getAdapter().getItemCount();
float proportion;
if (mHandleView.getY() == 0) {
proportion = 0f;
} else if (mHandleView.getY() + mHandleHeight >= mViewHeight - sTrackSnapRange) {
proportion = 1f;
} else {
proportion = y / (float) mViewHeight;
}
int scrolledItemCount = Math.round(proportion * itemCount);
if (isLayoutReversed(mRecyclerView.getLayoutManager())) {
scrolledItemCount = itemCount - scrolledItemCount;
}
int targetPos = getValueInRange(0, itemCount - 1, scrolledItemCount);
mRecyclerView.getLayoutManager().scrollToPosition(targetPos);
if (mShowBubble && mSectionIndexer != null) {
mBubbleView.setText(mSectionIndexer.getSectionText(targetPos));
}
}
}
private float getScrollProportion(RecyclerView recyclerView) {
if (recyclerView == null) {
return 0;
}
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
final float rangeDiff = verticalScrollRange - mViewHeight;
float proportion = (float) verticalScrollOffset / (rangeDiff > 0 ? rangeDiff : 1f);
return mViewHeight * proportion;
}
@SuppressWarnings("SameParameterValue")
private int getValueInRange(int min, int max, int value) {
int minimum = Math.max(min, value);
return Math.min(minimum, max);
}
private void setViewPositions(float y) {
mBubbleHeight = mBubbleView.getHeight();
mHandleHeight = mHandleView.getHeight();
int bubbleY = getValueInRange(0, mViewHeight - mBubbleHeight - mHandleHeight / 2, (int) (y - mBubbleHeight));
int handleY = getValueInRange(0, mViewHeight - mHandleHeight, (int) (y - mHandleHeight / 2));
if (mShowBubble) {
mBubbleView.setY(bubbleY);
}
mHandleView.setY(handleY);
}
private void updateViewHeights() {
int measureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mBubbleView.measure(measureSpec, measureSpec);
mBubbleHeight = mBubbleView.getMeasuredHeight();
mHandleView.measure(measureSpec, measureSpec);
mHandleHeight = mHandleView.getMeasuredHeight();
}
private boolean isLayoutReversed(@NonNull final RecyclerView.LayoutManager layoutManager) {
if (layoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) layoutManager).getReverseLayout();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
return ((StaggeredGridLayoutManager) layoutManager).getReverseLayout();
}
return false;
}
private boolean isViewVisible(View view) {
return view != null && view.getVisibility() == VISIBLE;
}
private void cancelAnimation(ViewPropertyAnimator animator) {
if (animator != null) {
animator.cancel();
}
}
private void showBubble() {
if (!isViewVisible(mBubbleView)) {
mBubbleView.setVisibility(VISIBLE);
mBubbleAnimator = mBubbleView.animate().alpha(1f)
.setDuration(sBubbleAnimDuration)
.setListener(new AnimatorListenerAdapter() {
// adapter required for new alpha value to stick
});
}
}
private void hideBubble() {
if (isViewVisible(mBubbleView)) {
mBubbleAnimator = mBubbleView.animate().alpha(0f)
.setDuration(sBubbleAnimDuration)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mBubbleView.setVisibility(GONE);
mBubbleAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
mBubbleView.setVisibility(GONE);
mBubbleAnimator = null;
}
});
}
}
private void showScrollbar() {
if (mRecyclerView.computeVerticalScrollRange() - mViewHeight > 0) {
float transX = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end);
mScrollbar.setTranslationX(transX);
mScrollbar.setVisibility(VISIBLE);
mScrollbarAnimator = mScrollbar.animate().translationX(0f).alpha(1f)
.setDuration(sScrollbarAnimDuration)
.setListener(new AnimatorListenerAdapter() {
// adapter required for new alpha value to stick
});
}
}
private void hideScrollbar() {
float transX = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end);
mScrollbarAnimator = mScrollbar.animate().translationX(transX).alpha(0f)
.setDuration(sScrollbarAnimDuration)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mScrollbar.setVisibility(GONE);
mScrollbarAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
mScrollbar.setVisibility(GONE);
mScrollbarAnimator = null;
}
});
}
private void setHandleSelected(boolean selected) {
mHandleView.setSelected(selected);
DrawableCompat.setTint(mHandleImage, selected ? mBubbleColor : mHandleColor);
}
@SuppressWarnings("ConstantConditions")
private void layout(Context context, AttributeSet attrs) {
inflate(context, R.layout.view_fastscroller, this);
setClipChildren(false);
setOrientation(HORIZONTAL);
mBubbleView = findViewById(R.id.fastscroll_bubble);
mHandleView = findViewById(R.id.fastscroll_handle);
mTrackView = findViewById(R.id.fastscroll_track);
mScrollbar = findViewById(R.id.fastscroll_scrollbar);
@ColorInt int bubbleColor = ColorUtil.adjustAlpha(context.getResources().getColor(R.color.colorAccent), 0.8f);
@ColorInt int handleColor = context.getResources().getColor(R.color.colorAccent);
@ColorInt int trackColor = context.getResources().getColor(R.color.transparent30);
@ColorInt int textColor = ColorUtil.isColorLight(bubbleColor) ? Color.BLACK : Color.WHITE;
boolean fadeScrollbar = true;
boolean showBubble = false;
boolean showTrack = true;
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, 0, 0);
if (typedArray != null) {
try {
bubbleColor = typedArray.getColor(R.styleable.FastScroller_bubbleColor, bubbleColor);
handleColor = typedArray.getColor(R.styleable.FastScroller_handleColor, handleColor);
trackColor = typedArray.getColor(R.styleable.FastScroller_trackColor, trackColor);
textColor = typedArray.getColor(R.styleable.FastScroller_bubbleTextColor, textColor);
fadeScrollbar = typedArray.getBoolean(R.styleable.FastScroller_fadeScrollbar, fadeScrollbar);
showBubble = typedArray.getBoolean(R.styleable.FastScroller_showBubble, showBubble);
showTrack = typedArray.getBoolean(R.styleable.FastScroller_showTrack, showTrack);
} finally {
typedArray.recycle();
}
}
}
setTrackColor(trackColor);
setHandleColor(handleColor);
setBubbleColor(bubbleColor);
setBubbleTextColor(textColor);
setFadeScrollbar(fadeScrollbar);
setBubbleVisible(showBubble);
setTrackVisible(showTrack);
}
public interface SectionIndexer {
String getSectionText(int position);
}
}

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2016 L4 Digital LLC. All rights reserved.
~
~ 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.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle">
<tools:solid android:color="#777777" />
<corners
android:topLeftRadius="@dimen/fastscroll_bubble_radius"
android:topRightRadius="@dimen/fastscroll_bubble_radius"
android:bottomLeftRadius="@dimen/fastscroll_bubble_radius"
android:bottomRightRadius="0dp" />
<size
android:height="@dimen/fastscroll_bubble_size"
android:width="@dimen/fastscroll_bubble_size" />
<padding
android:left="@dimen/fastscroll_bubble_padding"
android:right="@dimen/fastscroll_bubble_padding" />
</shape>

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2016 L4 Digital LLC. All rights reserved.
~
~ 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.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle">
<tools:solid android:color="#555555" />
<corners android:radius="@dimen/fastscroll_handle_radius" />
<size
android:height="@dimen/fastscroll_handle_height"
android:width="@dimen/fastscroll_handle_width" />
</shape>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2016 L4 Digital LLC. All rights reserved.
~
~ 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.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle">
<tools:solid android:color="#CCCCCC" />
<size android:width="@dimen/fastscroll_track_width" />
</shape>

@ -7,10 +7,15 @@
<include layout="@layout/toolbar"/>
<androidx.recyclerview.widget.RecyclerView
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<xyz.fycz.myreader.widget.scroller.FastScrollRecyclerView
android:id="@+id/rv_rule_list"
android:background="@color/colorForeground"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</LinearLayout>

@ -1,37 +1,38 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl_catalog"
android:layout_width="match_parent"
android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
tools:context="xyz.fycz.myreader.ui.fragment.CatalogFragment">
<!-- TODO: Update blank fragment layout -->
<ListView
android:id="@+id/lv_chapter_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:divider="@color/nothing"
android:fadeScrollbars="true"
android:fadingEdge="none"
android:fastScrollEnabled="true"
android:divider="@color/nothing"
android:listSelector="@color/nothing"
android:overScrollMode="never"
android:fadingEdge="none"
android:listSelector="@color/nothing"/>
android:scrollbars="vertical" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/change_sort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="60dp"
android:clickable="true"
android:contentDescription="倒序"
android:src="@mipmap/ic_order"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
app:borderWidth="0dp"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
/>
android:focusable="true" />
<ProgressBar
android:id="@+id/pb_loading"

@ -59,8 +59,8 @@
android:id="@+id/iv_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:layout_toStartOf="@+id/iv_menu"
android:background="?android:attr/selectableItemBackground"
android:paddingStart="5dp"
android:paddingEnd="5dp"
app:srcCompat="@drawable/ic_filter"
@ -70,9 +70,9 @@
android:id="@+id/iv_menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:background="?android:attr/selectableItemBackground"
android:paddingStart="5dp"
android:paddingEnd="5dp"
app:srcCompat="@drawable/ic_menu"
@ -85,9 +85,14 @@
android:layout_height="1dp"
android:background="@color/colorBackground" />
<androidx.recyclerview.widget.RecyclerView
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<xyz.fycz.myreader.widget.scroller.FastScrollRecyclerView
android:id="@+id/recycler_view"
android:background="@color/colorForeground"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</LinearLayout>

@ -63,9 +63,14 @@
android:background="@color/colorBackground"/>
<androidx.recyclerview.widget.RecyclerView
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<xyz.fycz.myreader.widget.scroller.FastScrollRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</LinearLayout>

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/fastscroll_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:gravity="center"
android:maxLines="1"
android:textSize="@dimen/fastscroll_bubble_textsize"
android:visibility="gone"
tools:background="@drawable/fastscroll_bubble"
tools:text="A"
tools:textColor="#ffffff"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/fastscroll_scrollbar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingEnd="@dimen/fastscroll_scrollbar_padding_end"
android:paddingLeft="@dimen/fastscroll_scrollbar_padding_start"
android:paddingRight="@dimen/fastscroll_scrollbar_padding_end"
android:paddingStart="@dimen/fastscroll_scrollbar_padding_start"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/fastscroll_track"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
tools:ignore="ContentDescription"
tools:src="@drawable/fastscroll_track" />
<ImageView
android:id="@+id/fastscroll_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
tools:ignore="ContentDescription"
tools:src="@drawable/fastscroll_handle" />
</FrameLayout>
</merge>

@ -54,4 +54,14 @@
<attr name="ios" format="boolean"/>
<attr name="leftSwipe" format="boolean"/>
</declare-styleable>
<declare-styleable name="FastScroller">
<attr name="fadeScrollbar" format="boolean" />
<attr name="showBubble" format="boolean" />
<attr name="showTrack" format="boolean" />
<attr name="trackColor" format="color" />
<attr name="handleColor" format="color" />
<attr name="bubbleColor" format="color" />
<attr name="bubbleTextColor" format="color" />
</declare-styleable>
</resources>

@ -48,4 +48,20 @@
<dimen name="_10dp">10dp</dimen>
<dimen name="_15dp">15dp</dimen>
<dimen name="fastscroll_bubble_radius">44dp</dimen>
<dimen name="fastscroll_bubble_size">88dp</dimen>
<dimen name="fastscroll_bubble_textsize">48sp</dimen>
<dimen name="fastscroll_bubble_padding">16dp</dimen>
<dimen name="fastscroll_handle_height">40dp</dimen>
<dimen name="fastscroll_handle_width">8dp</dimen>
<dimen name="fastscroll_handle_radius">0dp</dimen>
<dimen name="fastscroll_track_width">2dp</dimen>
<dimen name="fastscroll_scrollbar_margin_top">8dp</dimen>
<dimen name="fastscroll_scrollbar_margin_bottom">8dp</dimen>
<dimen name="fastscroll_scrollbar_padding_start">8dp</dimen>
<dimen name="fastscroll_scrollbar_padding_end">8dp</dimen>
</resources>

@ -6,4 +6,7 @@
<item name="tag2" type="id" />
<item type="id" name="status_bar"/>
<item name="fast_scroller" type="id" />
</resources>
Loading…
Cancel
Save