parent
a80eed8c11
commit
054b8cb325
@ -0,0 +1,36 @@ |
||||
<component name="InspectionProjectProfileManager"> |
||||
<profile version="1.0"> |
||||
<option name="myName" value="Project Default" /> |
||||
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true"> |
||||
<option name="TOP_LEVEL_CLASS_OPTIONS"> |
||||
<value> |
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||
<option name="REQUIRED_TAGS" value="" /> |
||||
</value> |
||||
</option> |
||||
<option name="INNER_CLASS_OPTIONS"> |
||||
<value> |
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||
<option name="REQUIRED_TAGS" value="" /> |
||||
</value> |
||||
</option> |
||||
<option name="METHOD_OPTIONS"> |
||||
<value> |
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" /> |
||||
</value> |
||||
</option> |
||||
<option name="FIELD_OPTIONS"> |
||||
<value> |
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||
<option name="REQUIRED_TAGS" value="" /> |
||||
</value> |
||||
</option> |
||||
<option name="IGNORE_DEPRECATED" value="false" /> |
||||
<option name="IGNORE_JAVADOC_PERIOD" value="true" /> |
||||
<option name="IGNORE_DUPLICATED_THROWS" value="false" /> |
||||
<option name="IGNORE_POINT_TO_ITSELF" value="false" /> |
||||
<option name="myAdditionalJavadocTags" value="date" /> |
||||
</inspection_tool> |
||||
</profile> |
||||
</component> |
@ -0,0 +1,10 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="RunConfigurationProducerService"> |
||||
<option name="ignoredProducers"> |
||||
<set> |
||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" /> |
||||
</set> |
||||
</option> |
||||
</component> |
||||
</project> |
@ -0,0 +1,86 @@ |
||||
package xyz.fycz.myreader.base |
||||
|
||||
import androidx.fragment.app.Fragment |
||||
import io.reactivex.disposables.CompositeDisposable |
||||
import io.reactivex.disposables.Disposable |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/22 11:46 |
||||
*/ |
||||
abstract class LazyFragment : Fragment() { |
||||
protected var mDisposable: CompositeDisposable? = null |
||||
/** |
||||
* 是否执行懒加载 |
||||
*/ |
||||
private var isLoaded = false |
||||
|
||||
/** |
||||
* 当前Fragment是否对用户可见 |
||||
*/ |
||||
private var isVisibleToUser = false |
||||
|
||||
/** |
||||
* 当使用ViewPager+Fragment形式会调用该方法时,setUserVisibleHint会优先Fragment生命周期函数调用, |
||||
* 所以这个时候就,会导致在setUserVisibleHint方法执行时就执行了懒加载, |
||||
* 而不是在onResume方法实际调用的时候执行懒加载。所以需要这个变量 |
||||
*/ |
||||
private var isCallResume = false |
||||
|
||||
/** |
||||
* 是否调用了setUserVisibleHint方法。处理show+add+hide模式下,默认可见 Fragment 不调用 |
||||
* onHiddenChanged 方法,进而不执行懒加载方法的问题。 |
||||
*/ |
||||
private var isCallUserVisibleHint = false |
||||
|
||||
protected open fun addDisposable(d: Disposable?) { |
||||
if (mDisposable == null) { |
||||
mDisposable = CompositeDisposable() |
||||
} |
||||
mDisposable!!.add(d!!) |
||||
} |
||||
|
||||
override fun onResume() { |
||||
super.onResume() |
||||
isCallResume = true |
||||
if (!isCallUserVisibleHint) isVisibleToUser = !isHidden |
||||
judgeLazyInit() |
||||
} |
||||
|
||||
|
||||
private fun judgeLazyInit() { |
||||
if (!isLoaded && isVisibleToUser && isCallResume) { |
||||
lazyInit() |
||||
isLoaded = true |
||||
} |
||||
} |
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) { |
||||
super.onHiddenChanged(hidden) |
||||
isVisibleToUser = !hidden |
||||
judgeLazyInit() |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
super.onDestroyView() |
||||
isLoaded = false |
||||
isVisibleToUser = false |
||||
isCallUserVisibleHint = false |
||||
isCallResume = false |
||||
} |
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) { |
||||
super.setUserVisibleHint(isVisibleToUser) |
||||
this.isVisibleToUser = isVisibleToUser |
||||
isCallUserVisibleHint = true |
||||
judgeLazyInit() |
||||
} |
||||
|
||||
override fun onDetach() { |
||||
super.onDetach() |
||||
if (mDisposable != null) { |
||||
mDisposable!!.clear() |
||||
} |
||||
} |
||||
abstract fun lazyInit() |
||||
} |
@ -0,0 +1,45 @@ |
||||
package xyz.fycz.myreader.entity; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/21 20:44 |
||||
*/ |
||||
public class FindKind { |
||||
private String tag; |
||||
private String name; |
||||
private String url; |
||||
private int maxPage; |
||||
|
||||
public String getTag() { |
||||
return tag; |
||||
} |
||||
|
||||
public void setTag(String tag) { |
||||
this.tag = tag; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getUrl() { |
||||
return url; |
||||
} |
||||
|
||||
public void setUrl(String url) { |
||||
this.url = url; |
||||
} |
||||
|
||||
public int getMaxPage() { |
||||
return maxPage; |
||||
} |
||||
|
||||
public void setMaxPage(int maxPage) { |
||||
this.maxPage = maxPage; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,162 @@ |
||||
package xyz.fycz.myreader.ui.activity; |
||||
|
||||
import android.os.Bundle; |
||||
import android.view.Menu; |
||||
import android.view.MenuItem; |
||||
import android.view.View; |
||||
|
||||
import androidx.appcompat.widget.Toolbar; |
||||
import androidx.fragment.app.Fragment; |
||||
import androidx.fragment.app.FragmentManager; |
||||
import androidx.fragment.app.FragmentPagerAdapter; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.disposables.Disposable; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.BaseActivity; |
||||
import xyz.fycz.myreader.base.BitIntentDataManager; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.databinding.ActivityFindBookBinding; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.ui.adapter.TabFragmentPageAdapter; |
||||
import xyz.fycz.myreader.ui.dialog.DialogCreator; |
||||
import xyz.fycz.myreader.ui.fragment.FindBook1Fragment; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.util.utils.RxUtils; |
||||
import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; |
||||
import xyz.fycz.myreader.webapi.crawler.find.QiDianFindCrawler; |
||||
import xyz.fycz.myreader.webapi.crawler.source.find.ThirdFindCrawler; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/21 20:10 |
||||
*/ |
||||
public class FindBookActivity extends BaseActivity { |
||||
private ActivityFindBookBinding binding; |
||||
private BookSource source; |
||||
private FindCrawler findCrawler; |
||||
private List<String> groups; |
||||
|
||||
@Override |
||||
protected void bindView() { |
||||
binding = ActivityFindBookBinding.inflate(getLayoutInflater()); |
||||
setContentView(binding.getRoot()); |
||||
} |
||||
|
||||
@Override |
||||
protected void initData(Bundle savedInstanceState) { |
||||
super.initData(savedInstanceState); |
||||
Object obj = BitIntentDataManager.getInstance().getData(getIntent()); |
||||
if (obj instanceof BookSource) { |
||||
source = (BookSource) obj; |
||||
findCrawler = new ThirdFindCrawler(source); |
||||
} else if (obj instanceof FindCrawler) { |
||||
findCrawler = (FindCrawler) obj; |
||||
} |
||||
if (findCrawler == null) { |
||||
finish(); |
||||
return; |
||||
} |
||||
initData(); |
||||
} |
||||
|
||||
@Override |
||||
protected void initWidget() { |
||||
binding.loading.setOnReloadingListener(this::initData); |
||||
} |
||||
|
||||
private void initData() { |
||||
findCrawler.initData() |
||||
.compose(RxUtils::toSimpleSingle) |
||||
.subscribe(new MyObserver<Boolean>() { |
||||
@Override |
||||
public void onSubscribe(Disposable d) { |
||||
addDisposable(d); |
||||
} |
||||
|
||||
@Override |
||||
public void onNext(@NotNull Boolean aBoolean) { |
||||
if (aBoolean) { |
||||
groups = findCrawler.getGroups(); |
||||
setUpToolbar(); |
||||
initFragments(); |
||||
} else { |
||||
ToastUtils.showError("发现规则语法错误"); |
||||
} |
||||
binding.loading.showFinish(); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
e.printStackTrace(); |
||||
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); |
||||
binding.loading.showError(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
protected void setUpToolbar(Toolbar toolbar) { |
||||
super.setUpToolbar(toolbar); |
||||
setStatusBarColor(R.color.colorPrimary, true); |
||||
} |
||||
|
||||
private void setUpToolbar() { |
||||
if (groups.size() == 1) { |
||||
binding.tabTlIndicator.setVisibility(View.GONE); |
||||
getSupportActionBar().setTitle(groups.get(0)); |
||||
} else { |
||||
binding.tabTlIndicator.setVisibility(View.VISIBLE); |
||||
} |
||||
} |
||||
|
||||
private void initFragments() { |
||||
TabFragmentPageAdapter adapter = new TabFragmentPageAdapter(getSupportFragmentManager()); |
||||
for (String group : groups) { |
||||
adapter.addFragment(new FindBook1Fragment(findCrawler.getKindsByKey(group), findCrawler), group); |
||||
} |
||||
binding.tabVp.setAdapter(adapter); |
||||
binding.tabVp.setOffscreenPageLimit(3); |
||||
binding.tabTlIndicator.setUpWithViewPager(binding.tabVp); |
||||
} |
||||
/********************************Event***************************************/ |
||||
/** |
||||
* 创建菜单 |
||||
* |
||||
* @param menu |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public boolean onCreateOptionsMenu(Menu menu) { |
||||
getMenuInflater().inflate(R.menu.menu_store, menu); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onPrepareOptionsMenu(Menu menu) { |
||||
if (findCrawler.needSearch()) { |
||||
menu.findItem(R.id.action_tip).setVisible(true); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 导航栏菜单点击事件 |
||||
* |
||||
* @param item |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public boolean onOptionsItemSelected(MenuItem item) { |
||||
if (item.getItemId() == R.id.action_tip) { |
||||
DialogCreator.createTipDialog(this, |
||||
getResources().getString(R.string.top_sort_tip, "此发现")); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,154 @@ |
||||
package xyz.fycz.myreader.ui.adapter.holder; |
||||
|
||||
import android.app.Activity; |
||||
import android.util.Log; |
||||
import android.view.View; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
import com.zhy.view.flowlayout.TagFlowLayout; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.App; |
||||
import xyz.fycz.myreader.base.adapter.ViewHolderImpl; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.entity.SearchBookBean; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager; |
||||
import xyz.fycz.myreader.ui.adapter.BookTagAdapter; |
||||
import xyz.fycz.myreader.util.help.StringHelper; |
||||
import xyz.fycz.myreader.util.utils.KeyWordUtils; |
||||
import xyz.fycz.myreader.util.utils.NetworkUtils; |
||||
import xyz.fycz.myreader.util.utils.RxUtils; |
||||
import xyz.fycz.myreader.webapi.BookApi; |
||||
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; |
||||
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; |
||||
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; |
||||
import xyz.fycz.myreader.widget.CoverImageView; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/22 9:34 |
||||
*/ |
||||
public class FindBookHolder extends ViewHolderImpl<Book> { |
||||
private List<String> tagList = new ArrayList<>(); |
||||
|
||||
private CoverImageView ivBookImg; |
||||
private TextView tvBookName; |
||||
private TagFlowLayout tflBookTag; |
||||
private TextView tvDesc; |
||||
private TextView tvAuthor; |
||||
private TextView tvSource; |
||||
private TextView tvNewestChapter; |
||||
|
||||
@Override |
||||
protected int getItemLayoutId() { |
||||
return R.layout.listview_search_book_item; |
||||
} |
||||
|
||||
@Override |
||||
public void initView() { |
||||
ivBookImg = findById(R.id.iv_book_img); |
||||
tvBookName = findById(R.id.tv_book_name); |
||||
tflBookTag = findById(R.id.tfl_book_tag); |
||||
tvAuthor = findById(R.id.tv_book_author); |
||||
tvDesc = findById(R.id.tv_book_desc); |
||||
tvSource = findById(R.id.tv_book_source); |
||||
tvNewestChapter = findById(R.id.tv_book_newest_chapter); |
||||
getItemView().setBackgroundColor(getContext().getResources().getColor(R.color.colorForeground)); |
||||
} |
||||
|
||||
@Override |
||||
public void onBind(RecyclerView.ViewHolder holder, Book data, int pos) { |
||||
BookSource source = BookSourceManager.getBookSourceByStr(data.getSource()); |
||||
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source); |
||||
if (StringHelper.isEmpty(data.getImgUrl())) { |
||||
data.setImgUrl(""); |
||||
} |
||||
if (!App.isDestroy((Activity) getContext())) { |
||||
ivBookImg.load(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), data.getImgUrl()), data.getName(), data.getAuthor()); |
||||
} |
||||
tvBookName.setText(data.getName()); |
||||
if (!StringHelper.isEmpty(data.getAuthor())) { |
||||
tvAuthor.setText(data.getAuthor()); |
||||
}else { |
||||
tvAuthor.setText(""); |
||||
} |
||||
initTagList(data); |
||||
if (!StringHelper.isEmpty(data.getNewestChapterTitle())) { |
||||
tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, data.getNewestChapterTitle())); |
||||
} else { |
||||
data.setNewestChapterTitle(""); |
||||
tvNewestChapter.setText(""); |
||||
} |
||||
if (!StringHelper.isEmpty(data.getDesc())) { |
||||
tvDesc.setText(String.format("简介:%s", data.getDesc())); |
||||
} else { |
||||
data.setDesc(""); |
||||
tvDesc.setText(""); |
||||
} |
||||
if (!StringHelper.isEmpty(source.getSourceName()) && !"未知书源".equals(source.getSourceName())) |
||||
tvSource.setText(String.format("书源:%s", source.getSourceName())); |
||||
if (needGetInfo(data) && rc instanceof BookInfoCrawler) { |
||||
Log.i(data.getName(), "initOtherInfo"); |
||||
BookInfoCrawler bic = (BookInfoCrawler) rc; |
||||
BookApi.getBookInfo(data, bic).compose(RxUtils::toSimpleSingle) |
||||
.subscribe(new MyObserver<Book>() { |
||||
@Override |
||||
public void onNext(@NotNull Book book) { |
||||
initOtherInfo(book, rc); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
private void initOtherInfo(Book book, ReadCrawler rc) { |
||||
//简介
|
||||
if (StringHelper.isEmpty(tvDesc.getText().toString())) { |
||||
tvDesc.setText(String.format("简介:%s", book.getDesc())); |
||||
} |
||||
if (StringHelper.isEmpty(tvNewestChapter.getText().toString())) { |
||||
tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle())); |
||||
} |
||||
if (!StringHelper.isEmpty(book.getAuthor())) { |
||||
tvAuthor.setText(book.getAuthor()); |
||||
} |
||||
//图片
|
||||
if (!App.isDestroy((Activity) getContext())) { |
||||
ivBookImg.load(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), book.getImgUrl()), book.getName(), book.getAuthor()); |
||||
} |
||||
} |
||||
|
||||
private void initTagList(Book data) { |
||||
tagList.clear(); |
||||
String type = data.getType(); |
||||
if (!StringHelper.isEmpty(type)) |
||||
tagList.add("0:" + type); |
||||
String wordCount = data.getWordCount(); |
||||
if (!StringHelper.isEmpty(wordCount)) |
||||
tagList.add("1:" + wordCount); |
||||
String status = data.getStatus(); |
||||
if (!StringHelper.isEmpty(status)) |
||||
tagList.add("2:" + status); |
||||
if (tagList.size() == 0) { |
||||
tflBookTag.setVisibility(View.GONE); |
||||
} else { |
||||
tflBookTag.setVisibility(View.VISIBLE); |
||||
tflBookTag.setAdapter(new BookTagAdapter(getContext(), tagList, 11)); |
||||
} |
||||
} |
||||
private boolean needGetInfo(Book bookBean) { |
||||
if (StringHelper.isEmpty(bookBean.getAuthor())) return true; |
||||
if (StringHelper.isEmpty(bookBean.getType())) return true; |
||||
if (StringHelper.isEmpty(bookBean.getDesc())) return true; |
||||
if (StringHelper.isEmpty(bookBean.getNewestChapterTitle())) return true; |
||||
return StringHelper.isEmpty(bookBean.getImgUrl()); |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
package xyz.fycz.myreader.ui.adapter.holder; |
||||
|
||||
import android.content.Intent; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.base.BitIntentDataManager; |
||||
import xyz.fycz.myreader.base.adapter.ViewHolderImpl; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.ui.activity.FindBookActivity; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/22 22:27 |
||||
*/ |
||||
public class FindSourceHolder extends ViewHolderImpl<BookSource> { |
||||
|
||||
private TextView tvName; |
||||
|
||||
@Override |
||||
protected int getItemLayoutId() { |
||||
return R.layout.item_find_source; |
||||
} |
||||
|
||||
@Override |
||||
public void initView() { |
||||
tvName = findById(R.id.tv_name); |
||||
} |
||||
|
||||
@Override |
||||
public void onBind(RecyclerView.ViewHolder holder, BookSource data, int pos) { |
||||
tvName.setText(data.getSourceName()); |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
package xyz.fycz.myreader.ui.fragment; |
||||
|
||||
import android.os.Bundle; |
||||
import android.view.Gravity; |
||||
import android.view.LayoutInflater; |
||||
import android.view.MenuItem; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.appcompat.widget.PopupMenu; |
||||
import androidx.fragment.app.Fragment; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.base.BaseFragment; |
||||
import xyz.fycz.myreader.base.LazyFragment; |
||||
import xyz.fycz.myreader.databinding.FragmentFindBook1Binding; |
||||
import xyz.fycz.myreader.entity.FindKind; |
||||
import xyz.fycz.myreader.ui.adapter.TabFragmentPageAdapter; |
||||
import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/21 23:06 |
||||
*/ |
||||
public class FindBook1Fragment extends LazyFragment { |
||||
private FragmentFindBook1Binding binding; |
||||
private List<FindKind> kinds; |
||||
private FindCrawler findCrawler; |
||||
private PopupMenu kindMenu; |
||||
|
||||
public FindBook1Fragment(List<FindKind> kinds, FindCrawler findCrawler) { |
||||
this.kinds = kinds; |
||||
this.findCrawler = findCrawler; |
||||
} |
||||
@Override |
||||
public void lazyInit() { |
||||
kindMenu = new PopupMenu(getContext(), binding.ivMenu, Gravity.END); |
||||
TabFragmentPageAdapter adapter = new TabFragmentPageAdapter(getChildFragmentManager()); |
||||
for (int i = 0, kindsSize = kinds.size(); i < kindsSize; i++) { |
||||
FindKind kind = kinds.get(i); |
||||
adapter.addFragment(new FindBook2Fragment(kind, findCrawler), kind.getName()); |
||||
kindMenu.getMenu().add(0, 0, i, kind.getName()); |
||||
} |
||||
binding.tabVp.setAdapter(adapter); |
||||
binding.tabVp.setOffscreenPageLimit(3); |
||||
binding.tabTlIndicator.setUpWithViewPager(binding.tabVp); |
||||
kindMenu.setOnMenuItemClickListener(item -> { |
||||
binding.tabTlIndicator.setCurrentItem(item.getOrder(), true); |
||||
return true; |
||||
}); |
||||
binding.ivMenu.setOnClickListener(v -> kindMenu.show()); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { |
||||
binding = FragmentFindBook1Binding.inflate(inflater, container, false); |
||||
return binding.getRoot(); |
||||
} |
||||
} |
@ -0,0 +1,177 @@ |
||||
package xyz.fycz.myreader.ui.fragment; |
||||
|
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import java.util.List; |
||||
|
||||
import io.reactivex.disposables.Disposable; |
||||
import xyz.fycz.myreader.base.BaseFragment; |
||||
import xyz.fycz.myreader.base.BitIntentDataManager; |
||||
import xyz.fycz.myreader.base.LazyFragment; |
||||
import xyz.fycz.myreader.base.adapter.BaseListAdapter; |
||||
import xyz.fycz.myreader.base.adapter.IViewHolder; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.databinding.FragmentFindBook2Binding; |
||||
import xyz.fycz.myreader.entity.FindKind; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
import xyz.fycz.myreader.greendao.service.BookService; |
||||
import xyz.fycz.myreader.ui.activity.BookDetailedActivity; |
||||
import xyz.fycz.myreader.ui.activity.BookstoreActivity; |
||||
import xyz.fycz.myreader.ui.adapter.holder.FindBookHolder; |
||||
import xyz.fycz.myreader.ui.dialog.SourceExchangeDialog; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
import xyz.fycz.myreader.util.utils.RxUtils; |
||||
import xyz.fycz.myreader.webapi.BookApi; |
||||
import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; |
||||
import xyz.fycz.myreader.widget.RefreshLayout; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/21 23:06 |
||||
*/ |
||||
public class FindBook2Fragment extends LazyFragment { |
||||
private FragmentFindBook2Binding binding; |
||||
private FindKind kind; |
||||
private FindCrawler findCrawler; |
||||
private int page = 1; |
||||
private BaseListAdapter<Book> findBookAdapter; |
||||
private SourceExchangeDialog mSourceDia; |
||||
|
||||
|
||||
public FindBook2Fragment(FindKind kind, FindCrawler findCrawler) { |
||||
this.kind = kind; |
||||
this.findCrawler = findCrawler; |
||||
} |
||||
|
||||
@Override |
||||
public void lazyInit() { |
||||
initData(); |
||||
initWidget(); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { |
||||
binding = FragmentFindBook2Binding.inflate(inflater, container, false); |
||||
return binding.getRoot(); |
||||
} |
||||
|
||||
protected void initData() { |
||||
findBookAdapter = new BaseListAdapter<Book>() { |
||||
@Override |
||||
protected IViewHolder<Book> createViewHolder(int viewType) { |
||||
return new FindBookHolder(); |
||||
} |
||||
}; |
||||
binding.rvFindBooks.setLayoutManager(new LinearLayoutManager(getContext())); |
||||
binding.rvFindBooks.setAdapter(findBookAdapter); |
||||
page = 1; |
||||
loadBooks(); |
||||
} |
||||
|
||||
protected void initWidget() { |
||||
binding.loading.setOnReloadingListener(() -> { |
||||
page = 1; |
||||
loadBooks(); |
||||
}); |
||||
//小说列表下拉加载更多事件
|
||||
binding.srlFindBooks.setOnLoadMoreListener(refreshLayout -> loadBooks()); |
||||
|
||||
//小说列表上拉刷新事件
|
||||
binding.srlFindBooks.setOnRefreshListener(refreshLayout -> { |
||||
page = 1; |
||||
loadBooks(); |
||||
}); |
||||
|
||||
findBookAdapter.setOnItemClickListener((view, pos) -> { |
||||
Book book = findBookAdapter.getItem(pos); |
||||
if (!findCrawler.needSearch()) { |
||||
goToBookDetail(book); |
||||
} else { |
||||
if (BookService.getInstance().isBookCollected(book)) { |
||||
goToBookDetail(book); |
||||
return; |
||||
} |
||||
mSourceDia = new SourceExchangeDialog(getActivity(), book); |
||||
mSourceDia.setOnSourceChangeListener((bean, pos1) -> { |
||||
Intent intent = new Intent(getContext(), BookDetailedActivity.class); |
||||
BitIntentDataManager.getInstance().putData(intent, mSourceDia.getaBooks()); |
||||
intent.putExtra(APPCONST.SOURCE_INDEX, pos1); |
||||
getActivity().startActivity(intent); |
||||
mSourceDia.dismiss(); |
||||
}); |
||||
mSourceDia.show(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void loadBooks() { |
||||
BookApi.findBooks(kind, findCrawler, page).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver<List<Book>>() { |
||||
@Override |
||||
public void onSubscribe(Disposable d) { |
||||
addDisposable(d); |
||||
} |
||||
|
||||
@Override |
||||
public void onNext(@NotNull List<Book> books) { |
||||
binding.loading.showFinish(); |
||||
if (page == 1) { |
||||
if (books.size() == 0) { |
||||
binding.srlFindBooks.finishRefreshWithNoMoreData(); |
||||
} else { |
||||
findBookAdapter.refreshItems(books); |
||||
binding.srlFindBooks.finishRefresh(); |
||||
} |
||||
} else { |
||||
if (books.size() == 0) { |
||||
binding.srlFindBooks.finishLoadMoreWithNoMoreData(); |
||||
} else { |
||||
findBookAdapter.addItems(books); |
||||
binding.srlFindBooks.finishLoadMore(); |
||||
} |
||||
} |
||||
page++; |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
e.printStackTrace(); |
||||
if (page == 1) { |
||||
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); |
||||
binding.loading.showError(); |
||||
binding.srlFindBooks.finishRefresh(); |
||||
} else { |
||||
if (e.getMessage()!= null && e.getMessage().contains("没有下一页")) { |
||||
binding.srlFindBooks.finishLoadMoreWithNoMoreData(); |
||||
} else { |
||||
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); |
||||
binding.srlFindBooks.finishLoadMore(); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* 前往书籍详情 |
||||
* |
||||
* @param book |
||||
*/ |
||||
private void goToBookDetail(Book book) { |
||||
Intent intent = new Intent(getContext(), BookDetailedActivity.class); |
||||
BitIntentDataManager.getInstance().putData(intent, book); |
||||
getActivity().startActivity(intent); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,43 @@ |
||||
package xyz.fycz.myreader.webapi.crawler.base; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import io.reactivex.Observable; |
||||
import xyz.fycz.myreader.entity.FindKind; |
||||
import xyz.fycz.myreader.entity.StrResponse; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/21 20:26 |
||||
*/ |
||||
public abstract class BaseFindCrawler implements FindCrawler { |
||||
protected Map<String, List<FindKind>> kindsMap = new LinkedHashMap<>(); |
||||
|
||||
@Override |
||||
public List<String> getGroups() { |
||||
return new ArrayList<>(kindsMap.keySet()); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, List<FindKind>> getKindsMap() { |
||||
return kindsMap; |
||||
} |
||||
|
||||
@Override |
||||
public List<FindKind> getKindsByKey(String key) { |
||||
return kindsMap.get(key); |
||||
} |
||||
|
||||
@Override |
||||
public abstract Observable<Boolean> initData(); |
||||
|
||||
@Override |
||||
public boolean needSearch() { |
||||
return false; |
||||
} |
||||
|
||||
} |
@ -1,31 +1,24 @@ |
||||
package xyz.fycz.myreader.webapi.crawler.base; |
||||
|
||||
import xyz.fycz.myreader.entity.bookstore.BookType; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import io.reactivex.Observable; |
||||
import xyz.fycz.myreader.entity.FindKind; |
||||
import xyz.fycz.myreader.entity.StrResponse; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2020/9/14 18:36 |
||||
* @date 2021/7/21 22:07 |
||||
*/ |
||||
public abstract class FindCrawler implements Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
public abstract String getCharset(); |
||||
public abstract String getFindName(); |
||||
public abstract String getFindUrl(); |
||||
public abstract boolean hasImg(); |
||||
public abstract boolean needSearch(); |
||||
//动态获取
|
||||
public List<BookType> getBookTypes(String html) { |
||||
return null; |
||||
} |
||||
//静态获取
|
||||
public List<BookType> getBookTypes(){ |
||||
return null; |
||||
} |
||||
public abstract List<Book> getFindBooks(String html, BookType bookType); |
||||
public abstract boolean getTypePage(BookType curType, int page); |
||||
public interface FindCrawler { |
||||
String getName(); |
||||
String getTag(); |
||||
List<String> getGroups(); |
||||
Map<String, List<FindKind>> getKindsMap(); |
||||
List<FindKind> getKindsByKey(String key); |
||||
Observable<Boolean> initData(); |
||||
boolean needSearch(); |
||||
Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind); |
||||
} |
||||
|
@ -0,0 +1,31 @@ |
||||
package xyz.fycz.myreader.webapi.crawler.base; |
||||
|
||||
import xyz.fycz.myreader.entity.bookstore.BookType; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2020/9/14 18:36 |
||||
*/ |
||||
public abstract class FindCrawler3 implements Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
public abstract String getCharset(); |
||||
public abstract String getFindName(); |
||||
public abstract String getFindUrl(); |
||||
public abstract boolean hasImg(); |
||||
public abstract boolean needSearch(); |
||||
//动态获取
|
||||
public List<BookType> getBookTypes(String html) { |
||||
return null; |
||||
} |
||||
//静态获取
|
||||
public List<BookType> getBookTypes(){ |
||||
return null; |
||||
} |
||||
public abstract List<Book> getFindBooks(String html, BookType bookType); |
||||
public abstract boolean getTypePage(BookType curType, int page); |
||||
} |
@ -0,0 +1,93 @@ |
||||
package xyz.fycz.myreader.webapi.crawler.find; |
||||
|
||||
import org.jsoup.Jsoup; |
||||
import org.jsoup.nodes.Document; |
||||
import org.jsoup.nodes.Element; |
||||
import org.jsoup.select.Elements; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import xyz.fycz.myreader.entity.FindKind; |
||||
import xyz.fycz.myreader.entity.StrResponse; |
||||
import xyz.fycz.myreader.entity.bookstore.BookType; |
||||
import xyz.fycz.myreader.enums.LocalBookSource; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
import xyz.fycz.myreader.webapi.crawler.base.BaseFindCrawler; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/22 17:11 |
||||
*/ |
||||
public class MiaoBiGeFindCrawler extends BaseFindCrawler { |
||||
private final LinkedHashMap<String, String> mBookTypes = new LinkedHashMap<>(); |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return "妙笔阁"; |
||||
} |
||||
|
||||
@Override |
||||
public String getTag() { |
||||
return "https://www.imiaobige.com"; |
||||
} |
||||
|
||||
private void initBookTypes() { |
||||
mBookTypes.put("玄幻奇幻", "https://www.imiaobige.com/xuanhuan/{page}.html"); |
||||
mBookTypes.put("武侠仙侠", "https://www.imiaobige.com/wuxia/{page}.html"); |
||||
mBookTypes.put("都市生活", "https://www.imiaobige.com/dushi/{page}.html"); |
||||
mBookTypes.put("历史军事", "https://www.imiaobige.com/lishi/{page}.html"); |
||||
mBookTypes.put("游戏竞技", "https://www.imiaobige.com/youxi/{page}.html"); |
||||
mBookTypes.put("科幻未来", "https://www.imiaobige.com/kehuan/{page}.html"); |
||||
} |
||||
|
||||
@Override |
||||
public Observable<Boolean> initData() { |
||||
return Observable.create(emitter -> { |
||||
initBookTypes(); |
||||
List<FindKind> kinds = new ArrayList<>(); |
||||
for (String name : mBookTypes.keySet()) { |
||||
FindKind findKind = new FindKind(); |
||||
findKind.setName(name); |
||||
findKind.setUrl(mBookTypes.get(name)); |
||||
findKind.setTag(getTag()); |
||||
kinds.add(findKind); |
||||
} |
||||
kindsMap.put(getName(), kinds); |
||||
emitter.onNext(true); |
||||
emitter.onComplete(); |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind) { |
||||
return Observable.create(emitter -> { |
||||
emitter.onNext(getFindBooks(strResponse.body(), kind)); |
||||
emitter.onComplete(); |
||||
}); |
||||
} |
||||
|
||||
public List<Book> getFindBooks(String html, FindKind kind) { |
||||
List<Book> books = new ArrayList<>(); |
||||
Document doc = Jsoup.parse(html); |
||||
Element div = doc.getElementById("sitebox"); |
||||
Elements dls = div.getElementsByTag("dl"); |
||||
for (Element dl : dls) { |
||||
Book book = new Book(); |
||||
Elements as = dl.getElementsByTag("a"); |
||||
book.setName(as.get(1).text()); |
||||
book.setAuthor(as.get(2).text()); |
||||
book.setType(kind.getName()); |
||||
book.setNewestChapterTitle(as.get(3).text()); |
||||
book.setDesc(dl.getElementsByClass("book_des").first().text()); |
||||
book.setImgUrl(as.first().getElementsByTag("img").attr("src")); |
||||
book.setChapterUrl(as.get(1).attr("href").replace("novel", "read").replace(".html", "/")); |
||||
book.setUpdateDate(dl.getElementsByClass("uptime").first().text()); |
||||
book.setSource(LocalBookSource.miaobi.toString()); |
||||
books.add(book); |
||||
} |
||||
return books; |
||||
} |
||||
} |
@ -0,0 +1,126 @@ |
||||
package xyz.fycz.myreader.webapi.crawler.find; |
||||
|
||||
import org.jsoup.Jsoup; |
||||
import org.jsoup.nodes.Document; |
||||
import org.jsoup.nodes.Element; |
||||
import org.jsoup.select.Elements; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import xyz.fycz.myreader.entity.bookstore.BookType; |
||||
import xyz.fycz.myreader.enums.LocalBookSource; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
import xyz.fycz.myreader.util.help.StringHelper; |
||||
import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; |
||||
|
||||
|
||||
public class QB5FindCrawler3 extends FindCrawler3 { |
||||
public static final String FIND_URL = "https://www.qb50.com"; |
||||
public static final String FIND_NAME = "书城[全本小说]"; |
||||
private static final String CHARSET = "GBK"; |
||||
|
||||
@Override |
||||
public String getCharset() { |
||||
return CHARSET; |
||||
} |
||||
|
||||
@Override |
||||
public String getFindName() { |
||||
return FIND_NAME; |
||||
} |
||||
|
||||
@Override |
||||
public String getFindUrl() { |
||||
return FIND_URL; |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasImg() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean needSearch() { |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 获取书城小说分类列表 |
||||
* |
||||
* @param html |
||||
* @return |
||||
*/ |
||||
public List<BookType> getBookTypes(String html) { |
||||
List<BookType> bookTypes = new ArrayList<>(); |
||||
Document doc = Jsoup.parse(html); |
||||
Elements divs = doc.getElementsByClass("nav_cont"); |
||||
if (divs.size() > 0) { |
||||
Elements uls = divs.get(0).getElementsByTag("ul"); |
||||
if (uls.size() > 0) { |
||||
for (Element li : uls.get(0).children()) { |
||||
Element a = li.child(0); |
||||
BookType bookType = new BookType(); |
||||
bookType.setTypeName(a.attr("title")); |
||||
bookType.setUrl(a.attr("href")); |
||||
if (bookType.getTypeName().contains("首页") || bookType.getTypeName().contains("热门小说")) |
||||
continue; |
||||
if (!StringHelper.isEmpty(bookType.getTypeName())) { |
||||
bookTypes.add(bookType); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
return bookTypes; |
||||
} |
||||
|
||||
|
||||
public List<Book> getFindBooks(String html, BookType bookType) { |
||||
List<Book> books = new ArrayList<>(); |
||||
Document doc = Jsoup.parse(html); |
||||
try { |
||||
int pageSize = Integer.parseInt(doc.getElementsByClass("last").first().text()); |
||||
bookType.setPageSize(pageSize); |
||||
} catch (Exception ignored) { |
||||
} |
||||
String type = doc.select("meta[name=keywords]").attr("content").replace(",全本小说网", ""); |
||||
Element div = doc.getElementById("tlist"); |
||||
Elements uls = div.getElementsByTag("ul"); |
||||
if (uls.size() > 0) { |
||||
for (Element li : uls.get(0).children()) { |
||||
Book book = new Book(); |
||||
Element aName = li.getElementsByClass("name").get(0); |
||||
Element divZz = li.getElementsByClass("zz").get(0); |
||||
Element divAuthor = li.getElementsByClass("author").get(0); |
||||
Element divSj = li.getElementsByClass("sj").get(0); |
||||
book.setType(type); |
||||
book.setName(aName.attr("title")); |
||||
book.setChapterUrl(aName.attr("href")); |
||||
book.setNewestChapterTitle(divZz.text()); |
||||
book.setAuthor(divAuthor.text()); |
||||
book.setUpdateDate(divSj.text()); |
||||
book.setSource(LocalBookSource.qb5.toString()); |
||||
books.add(book); |
||||
} |
||||
} |
||||
|
||||
return books; |
||||
} |
||||
|
||||
public boolean getTypePage(BookType curType, int page) { |
||||
if (curType.getPageSize() <= 0) { |
||||
curType.setPageSize(10); |
||||
} |
||||
if (page > curType.getPageSize()) { |
||||
return true; |
||||
} |
||||
if (!curType.getTypeName().equals("完本小说")) { |
||||
curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + "/"); |
||||
} else { |
||||
curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,238 @@ |
||||
package xyz.fycz.myreader.webapi.crawler.find; |
||||
|
||||
import org.json.JSONArray; |
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
|
||||
import java.text.SimpleDateFormat; |
||||
import java.util.ArrayList; |
||||
import java.util.Date; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
import java.util.Set; |
||||
|
||||
import io.reactivex.Observable; |
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.App; |
||||
import xyz.fycz.myreader.entity.FindKind; |
||||
import xyz.fycz.myreader.entity.StrResponse; |
||||
import xyz.fycz.myreader.entity.bookstore.BookType; |
||||
import xyz.fycz.myreader.entity.bookstore.QDBook; |
||||
import xyz.fycz.myreader.entity.bookstore.RankBook; |
||||
import xyz.fycz.myreader.entity.bookstore.SortBook; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
import xyz.fycz.myreader.util.SharedPreUtils; |
||||
import xyz.fycz.myreader.util.help.StringHelper; |
||||
import xyz.fycz.myreader.webapi.crawler.base.BaseFindCrawler; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/21 22:25 |
||||
*/ |
||||
public class QiDianFindCrawler extends BaseFindCrawler { |
||||
private String sourceUrl = "https://m.qidian.com"; |
||||
private String rankUrl = "https://m.qidian.com/majax/rank/{rankName}list?_csrfToken={cookie}&gender={sex}&pageNum={page}&catId=-1"; |
||||
private String sortUrl = "https://m.qidian.com/majax/category/list?_csrfToken={cookie}&gender={sex}&pageNum={page}&orderBy=&catId={catId}&subCatId="; |
||||
private String[] sex = {"male", "female"}; |
||||
private String yuepiaoParam = "&yearmonth={yearmonth}"; |
||||
private String imgUrl = "https://bookcover.yuewen.com/qdbimg/349573/{bid}/150"; |
||||
private String defaultCookie = "eXRDlZxmRDLvFAmdgzqvwWAASrxxp2WkVlH4ZM7e"; |
||||
private String yearmonthFormat = "yyyyMM"; |
||||
private LinkedHashMap<String, String> rankName = new LinkedHashMap<>(); |
||||
private LinkedHashMap<String, Integer> sortName = new LinkedHashMap<>(); |
||||
private boolean isFemale; |
||||
|
||||
public QiDianFindCrawler(boolean isFemale) { |
||||
this.isFemale = isFemale; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return isFemale ? "起点女生网" : "起点中文网"; |
||||
} |
||||
|
||||
@Override |
||||
public String getTag() { |
||||
return sourceUrl; |
||||
} |
||||
|
||||
@Override |
||||
public boolean needSearch() { |
||||
return true; |
||||
} |
||||
|
||||
private void initMaleRankName() { |
||||
if (!isFemale) { |
||||
rankName.put("风云榜", "yuepiao"); |
||||
rankName.put("畅销榜", "hotsales"); |
||||
rankName.put("阅读榜", "readIndex"); |
||||
rankName.put("粉丝榜", "newfans"); |
||||
rankName.put("推荐榜", "rec"); |
||||
rankName.put("更新榜", "update"); |
||||
rankName.put("签约榜", "sign"); |
||||
rankName.put("新书榜", "newbook"); |
||||
rankName.put("新人榜", "newauthor"); |
||||
} else { |
||||
rankName.put("风云榜", "yuepiao"); |
||||
rankName.put("阅读榜", "readIndex"); |
||||
rankName.put("粉丝榜", "newfans"); |
||||
rankName.put("推荐榜", "rec"); |
||||
rankName.put("更新榜", "update"); |
||||
rankName.put("收藏榜", "collect"); |
||||
rankName.put("免费榜", "free"); |
||||
} |
||||
} |
||||
|
||||
private void initSortNames() { |
||||
/* |
||||
{value: -1, text: "全站"} |
||||
1: {value: 21, text: "玄幻"} |
||||
2: {value: 1, text: "奇幻"} |
||||
3: {value: 2, text: "武侠"} |
||||
4: {value: 22, text: "仙侠"} |
||||
5: {value: 4, text: "都市"} |
||||
6: {value: 15, text: "现实"} |
||||
7: {value: 6, text: "军事"} |
||||
8: {value: 5, text: "历史"} |
||||
9: {value: 7, text: "游戏"} |
||||
10: {value: 8, text: "体育"} |
||||
11: {value: 9, text: "科幻"} |
||||
12: {value: 10, text: "悬疑"} |
||||
13: {value: 12, text: "轻小说"} |
||||
*/ |
||||
if (!isFemale) { |
||||
sortName.put("玄幻小说", 21); |
||||
sortName.put("奇幻小说", 1); |
||||
sortName.put("武侠小说", 2); |
||||
sortName.put("都市小说", 4); |
||||
sortName.put("现实小说", 15); |
||||
sortName.put("军事小说", 6); |
||||
sortName.put("历史小说", 5); |
||||
sortName.put("体育小说", 8); |
||||
sortName.put("科幻小说", 9); |
||||
sortName.put("悬疑小说", 10); |
||||
sortName.put("轻小说", 12); |
||||
sortName.put("短篇小说", 20076); |
||||
} else { |
||||
sortName.put("古代言情", 80); |
||||
sortName.put("仙侠奇缘", 81); |
||||
sortName.put("现代言情", 82); |
||||
sortName.put("烂漫青春", 83); |
||||
sortName.put("玄幻言情", 84); |
||||
sortName.put("悬疑推理", 85); |
||||
sortName.put("短篇小说", 30083); |
||||
sortName.put("科幻空间", 86); |
||||
sortName.put("游戏竞技", 88); |
||||
sortName.put("轻小说", 87); |
||||
sortName.put("现实生活", 30120); |
||||
} |
||||
} |
||||
|
||||
private List<FindKind> initKinds(boolean isSort) { |
||||
Set<String> names = !isSort ? rankName.keySet() : sortName.keySet(); |
||||
List<FindKind> kinds = new ArrayList<>(); |
||||
for (String name : names) { |
||||
FindKind kind = new FindKind(); |
||||
kind.setName(name); |
||||
String url; |
||||
if (!isSort) { |
||||
url = rankUrl.replace("{rankName}", rankName.get(name)); |
||||
kind.setMaxPage(30); |
||||
} else { |
||||
url = sortUrl.replace("{catId}", sortName.get(name) + ""); |
||||
kind.setMaxPage(5); |
||||
} |
||||
url = url.replace("{sex}", !isFemale ? sex[0] : sex[1]); |
||||
SharedPreUtils spu = SharedPreUtils.getInstance(); |
||||
String cookie = spu.getString(App.getmContext().getString(R.string.qdCookie), ""); |
||||
if (!cookie.equals("")) { |
||||
url = url.replace("{cookie}", StringHelper.getSubString(cookie, "_csrfToken=", ";")); |
||||
} else { |
||||
url = url.replace("{cookie}", defaultCookie); |
||||
} |
||||
if ("风云榜".equals(name)) { |
||||
SimpleDateFormat sdf = new SimpleDateFormat(yearmonthFormat, Locale.CHINA); |
||||
String yearmonth = sdf.format(new Date()); |
||||
url = url + yuepiaoParam.replace("{yearmonth}", yearmonth); |
||||
} |
||||
kind.setUrl(url); |
||||
kinds.add(kind); |
||||
} |
||||
return kinds; |
||||
} |
||||
|
||||
@Override |
||||
public Observable<Boolean> initData() { |
||||
return Observable.create(emitter -> { |
||||
initMaleRankName(); |
||||
initSortNames(); |
||||
kindsMap.put("排行榜", initKinds(false)); |
||||
kindsMap.put("分类", initKinds(true)); |
||||
emitter.onNext(true); |
||||
emitter.onComplete(); |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind) { |
||||
return Observable.create(emitter -> { |
||||
List<QDBook> qdBooks = getBooksFromJson(strResponse.body()); |
||||
emitter.onNext(convertQDBook2Book(qdBooks)); |
||||
emitter.onComplete(); |
||||
}); |
||||
} |
||||
|
||||
private List<QDBook> getBooksFromJson(String json) throws JSONException { |
||||
List<QDBook> books = new ArrayList<>(); |
||||
JSONObject all = new JSONObject(json); |
||||
JSONObject data = all.getJSONObject("data"); |
||||
int total = data.getInt("total"); |
||||
JSONArray jsonBooks = data.getJSONArray("records"); |
||||
for (int i = 0; i < jsonBooks.length(); i++) { |
||||
JSONObject jsonBook = jsonBooks.getJSONObject(i); |
||||
boolean isSort = jsonBook.has("state"); |
||||
QDBook book = !isSort ? new RankBook() : new SortBook(); |
||||
book.setbName(jsonBook.getString("bName")); |
||||
book.setbAuth(jsonBook.getString("bAuth")); |
||||
book.setBid(jsonBook.getString("bid")); |
||||
book.setCat(jsonBook.getString("cat")); |
||||
book.setCatId(jsonBook.getInt("catId")); |
||||
book.setCnt(jsonBook.getString("cnt")); |
||||
book.setDesc(jsonBook.getString("desc")); |
||||
book.setImg(imgUrl.replace("{bid}", jsonBook.getString("bid"))); |
||||
if (!isSort) { |
||||
((RankBook) book).setRankCnt(jsonBook.getString("rankCnt")); |
||||
((RankBook) book).setRankNum(jsonBook.getInt("rankNum")); |
||||
} else { |
||||
((SortBook) book).setState(jsonBook.getString("state")); |
||||
} |
||||
books.add(book); |
||||
} |
||||
return books; |
||||
} |
||||
|
||||
private List<Book> convertQDBook2Book(List<QDBook> qdBooks) { |
||||
List<Book> books = new ArrayList<>(); |
||||
for (QDBook rb : qdBooks) { |
||||
Book book = new Book(); |
||||
book.setName(rb.getbName()); |
||||
book.setAuthor(rb.getbAuth()); |
||||
book.setImgUrl(rb.getImg()); |
||||
String cat = rb.getCat(); |
||||
book.setType(cat.contains("小说") || cat.length() >= 4 ? cat : cat + "小说"); |
||||
// book.setNewestChapterTitle(rb.getDesc());
|
||||
book.setDesc(rb.getDesc()); |
||||
if (rb instanceof RankBook) { |
||||
boolean hasRankCnt = !((RankBook) rb).getRankCnt().equals("null"); |
||||
book.setWordCount(rb.getCnt()); |
||||
book.setStatus(hasRankCnt ? ((RankBook) rb).getRankCnt() : book.getType()); |
||||
} else if (rb instanceof SortBook) { |
||||
book.setWordCount(rb.getCnt()); |
||||
book.setStatus(((SortBook) rb).getState()); |
||||
} |
||||
books.add(book); |
||||
} |
||||
return books; |
||||
} |
||||
} |
@ -0,0 +1,120 @@ |
||||
package xyz.fycz.myreader.webapi.crawler.source.find; |
||||
|
||||
import android.text.TextUtils; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import javax.script.SimpleBindings; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.disposables.Disposable; |
||||
import xyz.fycz.myreader.entity.FindKind; |
||||
import xyz.fycz.myreader.entity.StrResponse; |
||||
import xyz.fycz.myreader.greendao.entity.Book; |
||||
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||
import xyz.fycz.myreader.greendao.entity.rule.FindRule; |
||||
import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager; |
||||
import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeRule; |
||||
import xyz.fycz.myreader.util.utils.StringUtils; |
||||
import xyz.fycz.myreader.webapi.crawler.base.BaseFindCrawler; |
||||
|
||||
import static xyz.fycz.myreader.common.APPCONST.SCRIPT_ENGINE; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/7/22 22:30 |
||||
*/ |
||||
public class ThirdFindCrawler extends BaseFindCrawler { |
||||
private BookSource source; |
||||
private FindRule findRuleBean; |
||||
private AnalyzeRule analyzeRule; |
||||
private String findError = "发现规则语法错误"; |
||||
|
||||
public ThirdFindCrawler(BookSource source) { |
||||
this.source = source; |
||||
findRuleBean = source.getFindRule(); |
||||
} |
||||
|
||||
public BookSource getSource() { |
||||
return source; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return source.getSourceName(); |
||||
} |
||||
|
||||
@Override |
||||
public String getTag() { |
||||
return source.getSourceUrl(); |
||||
} |
||||
|
||||
@Override |
||||
public Observable<Boolean> initData() { |
||||
return Observable.create(emitter -> { |
||||
try { |
||||
String[] kindA; |
||||
String findRule; |
||||
if (!TextUtils.isEmpty(findRuleBean.getUrl()) && !source.containsGroup(findError)) { |
||||
boolean isJs = findRuleBean.getUrl().startsWith("<js>"); |
||||
if (isJs) { |
||||
String jsStr = findRuleBean.getUrl().substring(4, findRuleBean.getUrl().lastIndexOf("<")); |
||||
findRule = evalJS(jsStr, source.getSourceUrl()).toString(); |
||||
} else { |
||||
findRule = findRuleBean.getUrl(); |
||||
} |
||||
kindA = findRule.split("(&&|\n)+"); |
||||
List<FindKind> children = new ArrayList<>(); |
||||
String groupName = getName(); |
||||
for (String kindB : kindA) { |
||||
if (kindB.trim().isEmpty()) continue; |
||||
String[] kind = kindB.split("::"); |
||||
if (kind.length == 1){ |
||||
if (children.size() > 0) { |
||||
kindsMap.put(groupName, children); |
||||
children = new ArrayList<>(); |
||||
} |
||||
groupName = kind[0].replaceAll("\\s", ""); |
||||
continue; |
||||
} |
||||
FindKind findKindBean = new FindKind(); |
||||
findKindBean.setTag(source.getSourceUrl()); |
||||
findKindBean.setName(kind[0]); |
||||
findKindBean.setUrl(kind[1]); |
||||
children.add(findKindBean); |
||||
} |
||||
kindsMap.put(groupName, children); |
||||
} |
||||
emitter.onNext(true); |
||||
} catch (Exception exception) { |
||||
source.addGroup(findError); |
||||
BookSourceManager.addBookSource(source); |
||||
emitter.onNext(false); |
||||
} |
||||
emitter.onComplete(); |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind) { |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* 执行JS |
||||
*/ |
||||
private Object evalJS(String jsStr, String baseUrl) throws Exception { |
||||
SimpleBindings bindings = new SimpleBindings(); |
||||
bindings.put("java", getAnalyzeRule()); |
||||
bindings.put("baseUrl", baseUrl); |
||||
return SCRIPT_ENGINE.eval(jsStr, bindings); |
||||
} |
||||
|
||||
private AnalyzeRule getAnalyzeRule() { |
||||
if (analyzeRule == null) { |
||||
analyzeRule = new AnalyzeRule(null); |
||||
} |
||||
return analyzeRule; |
||||
} |
||||
} |
@ -0,0 +1,201 @@ |
||||
package xyz.fycz.myreader.widget.loading; |
||||
|
||||
import android.animation.ValueAnimator; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.Color; |
||||
import android.graphics.ColorFilter; |
||||
import android.graphics.Paint; |
||||
import android.graphics.PixelFormat; |
||||
import android.graphics.Rect; |
||||
import android.graphics.drawable.Animatable; |
||||
import android.graphics.drawable.Drawable; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
|
||||
/** |
||||
* Created by Jack Wang on 2016/8/5. |
||||
*/ |
||||
|
||||
public abstract class Indicator extends Drawable implements Animatable { |
||||
|
||||
private HashMap<ValueAnimator,ValueAnimator.AnimatorUpdateListener> mUpdateListeners=new HashMap<>(); |
||||
|
||||
private ArrayList<ValueAnimator> mAnimators; |
||||
private int alpha = 255; |
||||
private static final Rect ZERO_BOUNDS_RECT = new Rect(); |
||||
protected Rect drawBounds = ZERO_BOUNDS_RECT; |
||||
|
||||
private boolean mHasAnimators; |
||||
|
||||
private Paint mPaint=new Paint(); |
||||
|
||||
public Indicator(){ |
||||
mPaint.setColor(Color.WHITE); |
||||
mPaint.setStyle(Paint.Style.FILL); |
||||
mPaint.setAntiAlias(true); |
||||
} |
||||
|
||||
public int getColor() { |
||||
return mPaint.getColor(); |
||||
} |
||||
|
||||
public void setColor(int color) { |
||||
mPaint.setColor(color); |
||||
} |
||||
|
||||
@Override |
||||
public void setAlpha(int alpha) { |
||||
this.alpha = alpha; |
||||
} |
||||
|
||||
@Override |
||||
public int getAlpha() { |
||||
return alpha; |
||||
} |
||||
|
||||
@Override |
||||
public int getOpacity() { |
||||
return PixelFormat.OPAQUE; |
||||
} |
||||
|
||||
@Override |
||||
public void setColorFilter(ColorFilter colorFilter) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void draw(Canvas canvas) { |
||||
draw(canvas,mPaint); |
||||
} |
||||
|
||||
public abstract void draw(Canvas canvas, Paint paint); |
||||
|
||||
public abstract ArrayList<ValueAnimator> onCreateAnimators(); |
||||
|
||||
@Override |
||||
public void start() { |
||||
ensureAnimators(); |
||||
|
||||
if (mAnimators == null) { |
||||
return; |
||||
} |
||||
|
||||
// If the animators has not ended, do nothing.
|
||||
if (isStarted()) { |
||||
return; |
||||
} |
||||
startAnimators(); |
||||
invalidateSelf(); |
||||
} |
||||
|
||||
private void startAnimators() { |
||||
for (int i = 0; i < mAnimators.size(); i++) { |
||||
ValueAnimator animator = mAnimators.get(i); |
||||
|
||||
//when the animator restart , add the updateListener again because they
|
||||
// was removed by animator stop .
|
||||
ValueAnimator.AnimatorUpdateListener updateListener=mUpdateListeners.get(animator); |
||||
if (updateListener!=null){ |
||||
animator.addUpdateListener(updateListener); |
||||
} |
||||
|
||||
animator.start(); |
||||
} |
||||
} |
||||
|
||||
private void stopAnimators() { |
||||
if (mAnimators!=null){ |
||||
for (ValueAnimator animator : mAnimators) { |
||||
if (animator != null && animator.isStarted()) { |
||||
animator.removeAllUpdateListeners(); |
||||
animator.end(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void ensureAnimators() { |
||||
if (!mHasAnimators) { |
||||
mAnimators = onCreateAnimators(); |
||||
mHasAnimators = true; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void stop() { |
||||
stopAnimators(); |
||||
} |
||||
|
||||
private boolean isStarted() { |
||||
for (ValueAnimator animator : mAnimators) { |
||||
return animator.isStarted(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isRunning() { |
||||
for (ValueAnimator animator : mAnimators) { |
||||
return animator.isRunning(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Your should use this to add AnimatorUpdateListener when |
||||
* create animator , otherwise , animator doesn't work when |
||||
* the animation restart . |
||||
* @param updateListener |
||||
*/ |
||||
public void addUpdateListener(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener){ |
||||
mUpdateListeners.put(animator,updateListener); |
||||
} |
||||
|
||||
@Override |
||||
protected void onBoundsChange(Rect bounds) { |
||||
super.onBoundsChange(bounds); |
||||
setDrawBounds(bounds); |
||||
} |
||||
|
||||
public void setDrawBounds(Rect drawBounds) { |
||||
setDrawBounds(drawBounds.left, drawBounds.top, drawBounds.right, drawBounds.bottom); |
||||
} |
||||
|
||||
public void setDrawBounds(int left, int top, int right, int bottom) { |
||||
this.drawBounds = new Rect(left, top, right, bottom); |
||||
} |
||||
|
||||
public void postInvalidate(){ |
||||
invalidateSelf(); |
||||
} |
||||
|
||||
public Rect getDrawBounds() { |
||||
return drawBounds; |
||||
} |
||||
|
||||
public int getWidth(){ |
||||
return drawBounds.width(); |
||||
} |
||||
|
||||
public int getHeight(){ |
||||
return drawBounds.height(); |
||||
} |
||||
|
||||
public int centerX(){ |
||||
return drawBounds.centerX(); |
||||
} |
||||
|
||||
public int centerY(){ |
||||
return drawBounds.centerY(); |
||||
} |
||||
|
||||
public float exactCenterX(){ |
||||
return drawBounds.exactCenterX(); |
||||
} |
||||
|
||||
public float exactCenterY(){ |
||||
return drawBounds.exactCenterY(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,420 @@ |
||||
package xyz.fycz.myreader.widget.loading; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.content.Context; |
||||
import android.content.res.TypedArray; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.Color; |
||||
import android.graphics.Rect; |
||||
import android.graphics.drawable.Animatable; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Build; |
||||
import android.text.TextUtils; |
||||
import android.util.AttributeSet; |
||||
import android.util.Log; |
||||
import android.view.View; |
||||
import android.view.animation.AnimationUtils; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.widget.loading.indicators.BallTrianglePathIndicator; |
||||
|
||||
|
||||
public class LoadingIndicatorView extends View { |
||||
|
||||
private static final String TAG="LoadingIndicatorView"; |
||||
|
||||
private static final BallTrianglePathIndicator DEFAULT_INDICATOR=new BallTrianglePathIndicator(); |
||||
|
||||
private static final int MIN_SHOW_TIME = 500; // ms
|
||||
private static final int MIN_DELAY = 500; // ms
|
||||
|
||||
private long mStartTime = -1; |
||||
|
||||
private boolean mPostedHide = false; |
||||
|
||||
private boolean mPostedShow = false; |
||||
|
||||
private boolean mDismissed = false; |
||||
|
||||
private final Runnable mDelayedHide = new Runnable() { |
||||
|
||||
@Override |
||||
public void run() { |
||||
mPostedHide = false; |
||||
mStartTime = -1; |
||||
setVisibility(View.GONE); |
||||
} |
||||
}; |
||||
|
||||
private final Runnable mDelayedShow = new Runnable() { |
||||
|
||||
@Override |
||||
public void run() { |
||||
mPostedShow = false; |
||||
if (!mDismissed) { |
||||
mStartTime = System.currentTimeMillis(); |
||||
setVisibility(View.VISIBLE); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
int mMinWidth; |
||||
int mMaxWidth; |
||||
int mMinHeight; |
||||
int mMaxHeight; |
||||
|
||||
private Indicator mIndicator; |
||||
private int mIndicatorColor; |
||||
|
||||
private boolean mShouldStartAnimationDrawable; |
||||
|
||||
public LoadingIndicatorView(Context context) { |
||||
super(context); |
||||
init(context, null,0,0); |
||||
} |
||||
|
||||
public LoadingIndicatorView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
init(context, attrs,0,R.style.LoadingIndicatorView); |
||||
} |
||||
|
||||
public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
init(context, attrs,defStyleAttr, R.style.LoadingIndicatorView); |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
||||
super(context, attrs, defStyleAttr, defStyleRes); |
||||
init(context,attrs,defStyleAttr,R.style.LoadingIndicatorView); |
||||
} |
||||
|
||||
private void init(Context context,AttributeSet attrs,int defStyleAttr, int defStyleRes) { |
||||
mMinWidth = 24; |
||||
mMaxWidth = 48; |
||||
mMinHeight = 24; |
||||
mMaxHeight = 48; |
||||
|
||||
final TypedArray a = context.obtainStyledAttributes( |
||||
attrs, R.styleable.AVLoadingIndicatorView, defStyleAttr, defStyleRes); |
||||
|
||||
mMinWidth = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_minWidth, mMinWidth); |
||||
mMaxWidth = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_maxWidth, mMaxWidth); |
||||
mMinHeight = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_minHeight, mMinHeight); |
||||
mMaxHeight = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_maxHeight, mMaxHeight); |
||||
String indicatorName=a.getString(R.styleable.AVLoadingIndicatorView_indicatorName); |
||||
mIndicatorColor=a.getColor(R.styleable.AVLoadingIndicatorView_indicatorColor, Color.WHITE); |
||||
setIndicator(indicatorName); |
||||
if (mIndicator==null){ |
||||
setIndicator(DEFAULT_INDICATOR); |
||||
} |
||||
a.recycle(); |
||||
} |
||||
|
||||
public Indicator getIndicator() { |
||||
return mIndicator; |
||||
} |
||||
|
||||
public void setIndicator(Indicator d) { |
||||
if (mIndicator != d) { |
||||
if (mIndicator != null) { |
||||
mIndicator.setCallback(null); |
||||
unscheduleDrawable(mIndicator); |
||||
} |
||||
|
||||
mIndicator = d; |
||||
//need to set indicator color again if you didn't specified when you update the indicator .
|
||||
setIndicatorColor(mIndicatorColor); |
||||
if (d != null) { |
||||
d.setCallback(this); |
||||
} |
||||
postInvalidate(); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* setIndicatorColor(0xFF00FF00) |
||||
* or |
||||
* setIndicatorColor(Color.BLUE) |
||||
* or |
||||
* setIndicatorColor(Color.parseColor("#FF4081")) |
||||
* or |
||||
* setIndicatorColor(0xFF00FF00) |
||||
* or |
||||
* setIndicatorColor(getResources().getColor(android.R.color.black)) |
||||
* @param color |
||||
*/ |
||||
public void setIndicatorColor(int color){ |
||||
this.mIndicatorColor=color; |
||||
mIndicator.setColor(color); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* You should pay attention to pass this parameter with two way: |
||||
* for example: |
||||
* 1. Only class Name,like "SimpleIndicator".(This way would use default package name with |
||||
* "com.wang.avi.indicators") |
||||
* 2. Class name with full package,like "com.my.android.indicators.SimpleIndicator". |
||||
* @param indicatorName the class must be extend Indicator . |
||||
*/ |
||||
public void setIndicator(String indicatorName){ |
||||
if (TextUtils.isEmpty(indicatorName)){ |
||||
return; |
||||
} |
||||
StringBuilder drawableClassName=new StringBuilder(); |
||||
if (!indicatorName.contains(".")){ |
||||
String defaultPackageName=getClass().getPackage().getName(); |
||||
drawableClassName.append(defaultPackageName) |
||||
.append(".indicators") |
||||
.append("."); |
||||
} |
||||
drawableClassName.append(indicatorName); |
||||
try { |
||||
Class<?> drawableClass = Class.forName(drawableClassName.toString()); |
||||
Indicator indicator = (Indicator) drawableClass.newInstance(); |
||||
setIndicator(indicator); |
||||
} catch (ClassNotFoundException e) { |
||||
Log.e(TAG,"Didn't find your class , check the name again !"); |
||||
} catch (InstantiationException e) { |
||||
e.printStackTrace(); |
||||
} catch (IllegalAccessException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
public void smoothToShow(){ |
||||
startAnimation(AnimationUtils.loadAnimation(getContext(),android.R.anim.fade_in)); |
||||
setVisibility(VISIBLE); |
||||
} |
||||
|
||||
public void smoothToHide(){ |
||||
startAnimation(AnimationUtils.loadAnimation(getContext(),android.R.anim.fade_out)); |
||||
setVisibility(GONE); |
||||
} |
||||
|
||||
public void hide() { |
||||
mDismissed = true; |
||||
removeCallbacks(mDelayedShow); |
||||
long diff = System.currentTimeMillis() - mStartTime; |
||||
if (diff >= MIN_SHOW_TIME || mStartTime == -1) { |
||||
// The progress spinner has been shown long enough
|
||||
// OR was not shown yet. If it wasn't shown yet,
|
||||
// it will just never be shown.
|
||||
setVisibility(View.GONE); |
||||
} else { |
||||
// The progress spinner is shown, but not long enough,
|
||||
// so put a delayed message in to hide it when its been
|
||||
// shown long enough.
|
||||
if (!mPostedHide) { |
||||
postDelayed(mDelayedHide, MIN_SHOW_TIME - diff); |
||||
mPostedHide = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void show() { |
||||
// Reset the start time.
|
||||
mStartTime = -1; |
||||
mDismissed = false; |
||||
removeCallbacks(mDelayedHide); |
||||
if (!mPostedShow) { |
||||
postDelayed(mDelayedShow, MIN_DELAY); |
||||
mPostedShow = true; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected boolean verifyDrawable(Drawable who) { |
||||
return who == mIndicator |
||||
|| super.verifyDrawable(who); |
||||
} |
||||
|
||||
void startAnimation() { |
||||
if (getVisibility() != VISIBLE) { |
||||
return; |
||||
} |
||||
|
||||
if (mIndicator instanceof Animatable) { |
||||
mShouldStartAnimationDrawable = true; |
||||
} |
||||
postInvalidate(); |
||||
} |
||||
|
||||
void stopAnimation() { |
||||
if (mIndicator instanceof Animatable) { |
||||
mIndicator.stop(); |
||||
mShouldStartAnimationDrawable = false; |
||||
} |
||||
postInvalidate(); |
||||
} |
||||
|
||||
@Override |
||||
public void setVisibility(int v) { |
||||
if (getVisibility() != v) { |
||||
super.setVisibility(v); |
||||
if (v == GONE || v == INVISIBLE) { |
||||
stopAnimation(); |
||||
} else { |
||||
startAnimation(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onVisibilityChanged(View changedView, int visibility) { |
||||
super.onVisibilityChanged(changedView, visibility); |
||||
if (visibility == GONE || visibility == INVISIBLE) { |
||||
stopAnimation(); |
||||
} else { |
||||
startAnimation(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void invalidateDrawable(Drawable dr) { |
||||
if (verifyDrawable(dr)) { |
||||
final Rect dirty = dr.getBounds(); |
||||
final int scrollX = getScrollX() + getPaddingLeft(); |
||||
final int scrollY = getScrollY() + getPaddingTop(); |
||||
|
||||
invalidate(dirty.left + scrollX, dirty.top + scrollY, |
||||
dirty.right + scrollX, dirty.bottom + scrollY); |
||||
} else { |
||||
super.invalidateDrawable(dr); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
||||
updateDrawableBounds(w, h); |
||||
} |
||||
|
||||
private void updateDrawableBounds(int w, int h) { |
||||
// onDraw will translate the canvas so we draw starting at 0,0.
|
||||
// Subtract out padding for the purposes of the calculations below.
|
||||
w -= getPaddingRight() + getPaddingLeft(); |
||||
h -= getPaddingTop() + getPaddingBottom(); |
||||
|
||||
int right = w; |
||||
int bottom = h; |
||||
int top = 0; |
||||
int left = 0; |
||||
|
||||
if (mIndicator != null) { |
||||
// Maintain aspect ratio. Certain kinds of animated drawables
|
||||
// get very confused otherwise.
|
||||
final int intrinsicWidth = mIndicator.getIntrinsicWidth(); |
||||
final int intrinsicHeight = mIndicator.getIntrinsicHeight(); |
||||
final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; |
||||
final float boundAspect = (float) w / h; |
||||
if (intrinsicAspect != boundAspect) { |
||||
if (boundAspect > intrinsicAspect) { |
||||
// New width is larger. Make it smaller to match height.
|
||||
final int width = (int) (h * intrinsicAspect); |
||||
left = (w - width) / 2; |
||||
right = left + width; |
||||
} else { |
||||
// New height is larger. Make it smaller to match width.
|
||||
final int height = (int) (w * (1 / intrinsicAspect)); |
||||
top = (h - height) / 2; |
||||
bottom = top + height; |
||||
} |
||||
} |
||||
mIndicator.setBounds(left, top, right, bottom); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected synchronized void onDraw(Canvas canvas) { |
||||
super.onDraw(canvas); |
||||
drawTrack(canvas); |
||||
} |
||||
|
||||
void drawTrack(Canvas canvas) { |
||||
final Drawable d = mIndicator; |
||||
if (d != null) { |
||||
// Translate canvas so a indeterminate circular progress bar with padding
|
||||
// rotates properly in its animation
|
||||
final int saveCount = canvas.save(); |
||||
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop()); |
||||
|
||||
d.draw(canvas); |
||||
canvas.restoreToCount(saveCount); |
||||
|
||||
if (mShouldStartAnimationDrawable && d instanceof Animatable) { |
||||
((Animatable) d).start(); |
||||
mShouldStartAnimationDrawable = false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||
int dw = 0; |
||||
int dh = 0; |
||||
|
||||
final Drawable d = mIndicator; |
||||
if (d != null) { |
||||
dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); |
||||
dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); |
||||
} |
||||
|
||||
updateDrawableState(); |
||||
|
||||
dw += getPaddingLeft() + getPaddingRight(); |
||||
dh += getPaddingTop() + getPaddingBottom(); |
||||
|
||||
final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0); |
||||
final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0); |
||||
setMeasuredDimension(measuredWidth, measuredHeight); |
||||
} |
||||
|
||||
@Override |
||||
protected void drawableStateChanged() { |
||||
super.drawableStateChanged(); |
||||
updateDrawableState(); |
||||
} |
||||
|
||||
private void updateDrawableState() { |
||||
final int[] state = getDrawableState(); |
||||
if (mIndicator != null && mIndicator.isStateful()) { |
||||
mIndicator.setState(state); |
||||
} |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
@Override |
||||
public void drawableHotspotChanged(float x, float y) { |
||||
super.drawableHotspotChanged(x, y); |
||||
|
||||
if (mIndicator != null) { |
||||
mIndicator.setHotspot(x, y); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onAttachedToWindow() { |
||||
super.onAttachedToWindow(); |
||||
startAnimation(); |
||||
removeCallbacks(); |
||||
} |
||||
|
||||
@Override |
||||
protected void onDetachedFromWindow() { |
||||
stopAnimation(); |
||||
// This should come after stopAnimation(), otherwise an invalidate message remains in the
|
||||
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
|
||||
super.onDetachedFromWindow(); |
||||
removeCallbacks(); |
||||
} |
||||
|
||||
private void removeCallbacks() { |
||||
removeCallbacks(mDelayedHide); |
||||
removeCallbacks(mDelayedShow); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,80 @@ |
||||
package xyz.fycz.myreader.widget.loading.indicators; |
||||
|
||||
import android.graphics.Canvas; |
||||
import android.graphics.Paint; |
||||
import android.view.animation.LinearInterpolator; |
||||
|
||||
import android.animation.ValueAnimator; |
||||
import xyz.fycz.myreader.widget.loading.Indicator; |
||||
|
||||
import java.util.ArrayList; |
||||
|
||||
/** |
||||
* Created by Jack on 2015/10/19. |
||||
*/ |
||||
public class BallTrianglePathIndicator extends Indicator { |
||||
|
||||
float[] translateX=new float[3],translateY=new float[3]; |
||||
|
||||
@Override |
||||
public void draw(Canvas canvas, Paint paint) { |
||||
paint.setStrokeWidth(3); |
||||
paint.setStyle(Paint.Style.STROKE); |
||||
for (int i = 0; i < 3; i++) { |
||||
canvas.save(); |
||||
canvas.translate(translateX[i], translateY[i]); |
||||
canvas.drawCircle(0, 0, getWidth() / 10, paint); |
||||
canvas.restore(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public ArrayList<ValueAnimator> onCreateAnimators() { |
||||
ArrayList<ValueAnimator> animators=new ArrayList<>(); |
||||
float startX=getWidth()/5; |
||||
float startY=getWidth()/5; |
||||
for (int i = 0; i < 3; i++) { |
||||
final int index=i; |
||||
ValueAnimator translateXAnim=ValueAnimator.ofFloat(getWidth()/2,getWidth()-startX,startX,getWidth()/2); |
||||
if (i==1){ |
||||
translateXAnim=ValueAnimator.ofFloat(getWidth()-startX,startX,getWidth()/2,getWidth()-startX); |
||||
}else if (i==2){ |
||||
translateXAnim=ValueAnimator.ofFloat(startX,getWidth()/2,getWidth()-startX,startX); |
||||
} |
||||
ValueAnimator translateYAnim=ValueAnimator.ofFloat(startY,getHeight()-startY,getHeight()-startY,startY); |
||||
if (i==1){ |
||||
translateYAnim=ValueAnimator.ofFloat(getHeight()-startY,getHeight()-startY,startY,getHeight()-startY); |
||||
}else if (i==2){ |
||||
translateYAnim=ValueAnimator.ofFloat(getHeight()-startY,startY,getHeight()-startY,getHeight()-startY); |
||||
} |
||||
|
||||
translateXAnim.setDuration(2000); |
||||
translateXAnim.setInterpolator(new LinearInterpolator()); |
||||
translateXAnim.setRepeatCount(-1); |
||||
addUpdateListener(translateXAnim,new ValueAnimator.AnimatorUpdateListener() { |
||||
@Override |
||||
public void onAnimationUpdate(ValueAnimator animation) { |
||||
translateX [index]= (float) animation.getAnimatedValue(); |
||||
postInvalidate(); |
||||
} |
||||
}); |
||||
|
||||
translateYAnim.setDuration(2000); |
||||
translateYAnim.setInterpolator(new LinearInterpolator()); |
||||
translateYAnim.setRepeatCount(-1); |
||||
addUpdateListener(translateYAnim,new ValueAnimator.AnimatorUpdateListener() { |
||||
@Override |
||||
public void onAnimationUpdate(ValueAnimator animation) { |
||||
translateY [index]= (float) animation.getAnimatedValue(); |
||||
postInvalidate(); |
||||
} |
||||
}); |
||||
|
||||
animators.add(translateXAnim); |
||||
animators.add(translateYAnim); |
||||
} |
||||
return animators; |
||||
} |
||||
|
||||
|
||||
} |
@ -1,18 +0,0 @@ |
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:width="24dp" |
||||
android:height="24dp" |
||||
android:viewportWidth="1024" |
||||
android:viewportHeight="1024"> |
||||
<path |
||||
android:pathData="M348.16,153.6m40.96,0l245.76,0q40.96,0 40.96,40.96l0,286.72q0,40.96 -40.96,40.96l-245.76,0q-40.96,0 -40.96,-40.96l0,-286.72q0,-40.96 40.96,-40.96Z" |
||||
android:fillColor="#689CD5"/> |
||||
<path |
||||
android:pathData="M174.08,409.6m40.96,0l593.92,0q40.96,0 40.96,40.96l0,378.88q0,40.96 -40.96,40.96l-593.92,0q-40.96,0 -40.96,-40.96l0,-378.88q0,-40.96 40.96,-40.96Z" |
||||
android:fillColor="#81B7EF"/> |
||||
<path |
||||
android:pathData="M122.88,588.8m40.96,0l696.32,0q40.96,0 40.96,40.96l0,199.68q0,40.96 -40.96,40.96l-696.32,0q-40.96,0 -40.96,-40.96l0,-199.68q0,-40.96 40.96,-40.96Z" |
||||
android:fillColor="#689CD5"/> |
||||
<path |
||||
android:pathData="M511.99,481.51C479.65,507.55 458.66,516.92 449.02,509.59c-9.64,-7.31 -7.6,-31.05 6.12,-71.22 -33.6,-24.41 -48.61,-42.36 -45.03,-53.86 3.58,-11.51 25.83,-16.82 66.74,-15.93C488.57,327.66 500.28,307.2 511.99,307.2s23.42,20.46 35.13,61.38c40.83,-1.17 63.08,4.15 66.74,15.93 3.67,11.79 -11.35,29.74 -45.03,53.86 13.34,40.45 15.38,64.19 6.12,71.22 -9.27,7.03 -30.25,-2.33 -62.96,-28.09z" |
||||
android:fillColor="#E2EEFD"/> |
||||
</vector> |
@ -1,9 +0,0 @@ |
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:width="200dp" |
||||
android:height="200dp" |
||||
android:viewportWidth="1024" |
||||
android:viewportHeight="1024"> |
||||
<path |
||||
android:pathData="M320,512 L192,512c-70.4,0 -128,57.6 -128,128l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128l0,-128C448,569.6 390.4,512 320,512zM384,768c0,35.2 -28.8,64 -64,64L192,832c-35.2,0 -64,-28.8 -64,-64l0,-128c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L384,768zM768,64l-128,0C569.6,64 512,121.6 512,192l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128L896,192C896,121.6 838.4,64 768,64zM832,320c0,35.2 -28.8,64 -64,64l-128,0C604.8,384 576,355.2 576,320L576,192c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L832,320zM768,512l-128,0c-70.4,0 -128,57.6 -128,128l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128l0,-128C896,569.6 838.4,512 768,512zM832,768c0,35.2 -28.8,64 -64,64l-128,0c-35.2,0 -64,-28.8 -64,-64l0,-128c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L832,768zM320,64 L192,64C121.6,64 64,121.6 64,192l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128L448,192C448,121.6 390.4,64 320,64zM384,320c0,35.2 -28.8,64 -64,64L192,384C156.8,384 128,355.2 128,320L128,192c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L384,320z" |
||||
android:fillColor="#689CD5"/> |
||||
</vector> |
@ -0,0 +1,50 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:orientation="vertical"> |
||||
<!--指示器--> |
||||
<com.google.android.material.appbar.AppBarLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content"> |
||||
|
||||
<androidx.appcompat.widget.Toolbar |
||||
android:id="@+id/toolbar" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="45dp" |
||||
android:background="@color/colorPrimary" |
||||
android:fitsSystemWindows="true" |
||||
android:minHeight="?attr/actionBarSize" |
||||
android:theme="?attr/actionBarStyle" |
||||
app:subtitleTextAppearance="@style/toolbar_subtitle_textStyle" |
||||
app:titleTextAppearance="@style/toolbar_title_textStyle"> |
||||
<com.nshmura.recyclertablayout.RecyclerTabLayout |
||||
android:id="@+id/tab_tl_indicator" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:layout_weight="9" |
||||
app:rtl_tabIndicatorColor="@color/colorAccent" |
||||
app:rtl_tabIndicatorHeight="2dp" |
||||
app:rtl_tabBackground="?attr/selectableItemBackground" |
||||
app:rtl_tabTextAppearance="@style/TabLayoutTextStyle" |
||||
app:rtl_tabSelectedTextColor="@color/textPrimaryInverted" |
||||
app:rtl_tabPaddingStart="12dp" |
||||
app:rtl_tabPaddingEnd="12dp" |
||||
app:rtl_scrollEnabled="true"/> |
||||
</androidx.appcompat.widget.Toolbar> |
||||
|
||||
</com.google.android.material.appbar.AppBarLayout> |
||||
|
||||
<xyz.fycz.myreader.widget.RefreshLayout |
||||
android:id="@+id/loading" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:orientation="vertical"> |
||||
|
||||
<androidx.viewpager.widget.ViewPager |
||||
android:id="@+id/tab_vp" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" /> |
||||
</xyz.fycz.myreader.widget.RefreshLayout> |
||||
</LinearLayout> |
@ -0,0 +1,47 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
xmlns:tl="http://schemas.android.com/apk/res-auto" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:background="@color/colorForeground" |
||||
android:orientation="vertical"> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="35dp" |
||||
android:orientation="horizontal"> |
||||
|
||||
<com.nshmura.recyclertablayout.RecyclerTabLayout |
||||
android:id="@+id/tab_tl_indicator" |
||||
android:layout_weight="10" |
||||
android:layout_width="0dp" |
||||
android:layout_height="match_parent" |
||||
app:rtl_tabIndicatorColor="@color/colorAccent" |
||||
app:rtl_tabIndicatorHeight="2dp" |
||||
app:rtl_tabBackground="?attr/selectableItemBackground" |
||||
app:rtl_tabTextAppearance="@android:style/TextAppearance.Small" |
||||
app:rtl_tabSelectedTextColor="?android:textColorPrimary" |
||||
app:rtl_tabPaddingStart="12dp" |
||||
app:rtl_tabPaddingEnd="12dp" |
||||
app:rtl_scrollEnabled="true"/> |
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView |
||||
android:id="@+id/iv_menu" |
||||
android:layout_width="0dp" |
||||
android:layout_height="match_parent" |
||||
android:layout_gravity="end" |
||||
android:layout_weight="1" |
||||
android:background="?android:attr/selectableItemBackground" |
||||
android:layout_marginStart="5dp" |
||||
android:paddingHorizontal="5dp" |
||||
app:srcCompat="@drawable/ic_menu" |
||||
app:tint="@color/colorAccent" /> |
||||
</LinearLayout> |
||||
|
||||
|
||||
<androidx.viewpager.widget.ViewPager |
||||
android:id="@+id/tab_vp" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" /> |
||||
</LinearLayout> |
@ -0,0 +1,33 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<xyz.fycz.myreader.widget.RefreshLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:id="@+id/loading" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:orientation="vertical"> |
||||
|
||||
<com.scwang.smartrefresh.layout.SmartRefreshLayout |
||||
android:id="@+id/srl_find_books" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:descendantFocusability="blocksDescendants"> |
||||
|
||||
<com.scwang.smartrefresh.header.MaterialHeader |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" /> |
||||
<androidx.recyclerview.widget.RecyclerView |
||||
android:id="@+id/rv_find_books" |
||||
android:layout_marginHorizontal="5dp" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" /> |
||||
<com.scwang.smartrefresh.layout.footer.ClassicsFooter |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
app:srlTextLoading="@string/loading_tip" |
||||
app:srlTextFailed="@string/loading_tip" |
||||
app:srlTextNothing="总得有个结尾吧" |
||||
app:srlAccentColor="@color/textSecondary"/> |
||||
</com.scwang.smartrefresh.layout.SmartRefreshLayout> |
||||
|
||||
</xyz.fycz.myreader.widget.RefreshLayout> |
@ -0,0 +1,20 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/rl_find_source" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="50dp" |
||||
android:background="@drawable/selector_common_bg" |
||||
android:paddingLeft="20dp" |
||||
android:paddingRight="20dp"> |
||||
|
||||
<TextView |
||||
android:id="@+id/tv_name" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerVertical="true" |
||||
android:text="@string/app_name" |
||||
android:textColor="@color/textSecondary" |
||||
android:textSize="@dimen/text_normal_size" /> |
||||
|
||||
</RelativeLayout> |
@ -1,92 +1,96 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:orientation="horizontal" |
||||
android:background="@color/colorBackground" |
||||
android:padding="3dp"> |
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:background="@color/colorBackground" |
||||
android:orientation="horizontal" |
||||
android:padding="3dp"> |
||||
|
||||
<xyz.fycz.myreader.widget.CoverImageView |
||||
android:id="@+id/iv_book_img" |
||||
android:layout_width="42dp" |
||||
android:layout_height="60dp" |
||||
android:scaleType="fitXY" |
||||
app:srcCompat="@mipmap/default_cover" |
||||
android:visibility="gone"/> |
||||
android:id="@+id/iv_book_img" |
||||
android:layout_width="42dp" |
||||
android:layout_height="60dp" |
||||
android:scaleType="fitXY" |
||||
android:visibility="gone" |
||||
app:srcCompat="@mipmap/default_cover" /> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="vertical" |
||||
android:paddingLeft="2dp" |
||||
android:paddingEnd="2dp"> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:paddingLeft="2dp" |
||||
android:paddingEnd="2dp" |
||||
android:orientation="vertical"> |
||||
android:orientation="horizontal" |
||||
android:padding="2dp"> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:padding="2dp" |
||||
android:orientation="horizontal"> |
||||
<TextView |
||||
android:id="@+id/tv_book_name" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="18dp" |
||||
android:text="bookname" |
||||
android:textSize="15sp" |
||||
android:textColor="@color/textPrimary"/> |
||||
<TextView |
||||
android:id="@+id/tv_book_author" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:paddingStart="5dp" |
||||
android:text="author" |
||||
android:textColor="@color/textSecondary" |
||||
android:maxLines="1" |
||||
android:textSize="12sp"/> |
||||
android:id="@+id/tv_book_name" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="18dp" |
||||
android:text="bookname" |
||||
android:textColor="@color/textPrimary" |
||||
android:textSize="15sp" /> |
||||
|
||||
</LinearLayout> |
||||
<TextView |
||||
android:id="@+id/tv_book_newest_chapter" |
||||
<TextView |
||||
android:id="@+id/tv_book_author" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:padding="2dp" |
||||
android:text="newest_chapter" |
||||
android:ellipsize="end" |
||||
android:textColor="@color/textSecondary" |
||||
android:maxLines="1" |
||||
android:textSize="12sp"/> |
||||
android:paddingStart="5dp" |
||||
android:text="author" |
||||
android:textColor="@color/textSecondary" |
||||
android:textSize="12sp" /> |
||||
|
||||
</LinearLayout> |
||||
|
||||
<TextView |
||||
android:id="@+id/tv_book_newest_chapter" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:ellipsize="end" |
||||
android:maxLines="1" |
||||
android:padding="2dp" |
||||
android:text="newest_chapter" |
||||
android:textColor="@color/textSecondary" |
||||
android:textSize="12sp" /> |
||||
|
||||
<RelativeLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:padding="2dp" |
||||
android:orientation="horizontal"> |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="horizontal" |
||||
android:padding="2dp"> |
||||
|
||||
|
||||
<TextView |
||||
android:id="@+id/tv_book_time" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="time" |
||||
android:textColor="@color/textSecondary" |
||||
android:maxLines="1" |
||||
android:textSize="12sp"/> |
||||
android:id="@+id/tv_book_time" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:maxLines="1" |
||||
android:text="time" |
||||
android:textColor="@color/textSecondary" |
||||
android:textSize="12sp" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/tv_book_source" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_alignParentEnd="true" |
||||
android:textColor="@color/textSecondary" |
||||
android:text="book source" |
||||
android:maxLines="1" |
||||
android:textSize="12sp"/> |
||||
android:id="@+id/tv_book_source" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_alignParentEnd="true" |
||||
android:maxLines="1" |
||||
android:text="book source" |
||||
android:textColor="@color/textSecondary" |
||||
android:textSize="12sp" /> |
||||
</RelativeLayout> |
||||
|
||||
|
||||
<View |
||||
android:layout_width="fill_parent" |
||||
android:layout_height="0.5dp" |
||||
android:background="@color/colorDivider"/> |
||||
android:layout_width="fill_parent" |
||||
android:layout_height="0.5dp" |
||||
android:background="@color/colorDivider" /> |
||||
|
||||
</LinearLayout> |
||||
</LinearLayout> |
@ -1,20 +1,22 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
|
||||
<ProgressBar |
||||
android:id="@+id/loadding_pb" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerInParent="true"/> |
||||
<xyz.fycz.myreader.widget.loading.LoadingIndicatorView |
||||
android:id="@+id/loading" |
||||
style="@style/LoadingIndicatorView" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerInParent="true" /> |
||||
|
||||
<TextView |
||||
android:paddingTop="5dp" |
||||
android:layout_below="@+id/loadding_pb" |
||||
android:layout_centerInParent="true" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:textColor="@color/textSecondary" |
||||
android:text="@string/loading_tip"/> |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_below="@+id/loading" |
||||
android:layout_centerInParent="true" |
||||
android:paddingTop="10dp" |
||||
android:text="@string/loading_tip" |
||||
android:textSize="@dimen/text_normal_size" |
||||
android:textColor="@color/textPrimary" /> |
||||
</RelativeLayout> |
Loading…
Reference in new issue