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; |
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.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 |
* @author fengyue |
||||||
* @date 2020/9/14 18:36 |
* @date 2021/7/21 22:07 |
||||||
*/ |
*/ |
||||||
public abstract class FindCrawler implements Serializable { |
public interface FindCrawler { |
||||||
private static final long serialVersionUID = 1L; |
String getName(); |
||||||
|
String getTag(); |
||||||
public abstract String getCharset(); |
List<String> getGroups(); |
||||||
public abstract String getFindName(); |
Map<String, List<FindKind>> getKindsMap(); |
||||||
public abstract String getFindUrl(); |
List<FindKind> getKindsByKey(String key); |
||||||
public abstract boolean hasImg(); |
Observable<Boolean> initData(); |
||||||
public abstract boolean needSearch(); |
boolean needSearch(); |
||||||
//动态获取
|
Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind); |
||||||
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,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"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<LinearLayout |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
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_width="match_parent" |
||||||
android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" |
android:layout_height="wrap_content" |
||||||
android:orientation="horizontal" |
android:background="@color/colorBackground" |
||||||
android:background="@color/colorBackground" |
android:orientation="horizontal" |
||||||
android:padding="3dp"> |
android:padding="3dp"> |
||||||
|
|
||||||
<xyz.fycz.myreader.widget.CoverImageView |
<xyz.fycz.myreader.widget.CoverImageView |
||||||
android:id="@+id/iv_book_img" |
android:id="@+id/iv_book_img" |
||||||
android:layout_width="42dp" |
android:layout_width="42dp" |
||||||
android:layout_height="60dp" |
android:layout_height="60dp" |
||||||
android:scaleType="fitXY" |
android:scaleType="fitXY" |
||||||
app:srcCompat="@mipmap/default_cover" |
android:visibility="gone" |
||||||
android:visibility="gone"/> |
app:srcCompat="@mipmap/default_cover" /> |
||||||
|
|
||||||
<LinearLayout |
<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_width="match_parent" |
||||||
android:layout_height="wrap_content" |
android:layout_height="wrap_content" |
||||||
android:paddingLeft="2dp" |
android:orientation="horizontal" |
||||||
android:paddingEnd="2dp" |
android:padding="2dp"> |
||||||
android:orientation="vertical"> |
|
||||||
|
|
||||||
<LinearLayout |
|
||||||
android:layout_width="match_parent" |
|
||||||
android:layout_height="wrap_content" |
|
||||||
android:padding="2dp" |
|
||||||
android:orientation="horizontal"> |
|
||||||
<TextView |
<TextView |
||||||
android:id="@+id/tv_book_name" |
android:id="@+id/tv_book_name" |
||||||
android:layout_width="wrap_content" |
android:layout_width="wrap_content" |
||||||
android:layout_height="18dp" |
android:layout_height="18dp" |
||||||
android:text="bookname" |
android:text="bookname" |
||||||
android:textSize="15sp" |
android:textColor="@color/textPrimary" |
||||||
android:textColor="@color/textPrimary"/> |
android:textSize="15sp" /> |
||||||
<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"/> |
|
||||||
|
|
||||||
</LinearLayout> |
<TextView |
||||||
<TextView |
android:id="@+id/tv_book_author" |
||||||
android:id="@+id/tv_book_newest_chapter" |
|
||||||
android:layout_width="wrap_content" |
android:layout_width="wrap_content" |
||||||
android:layout_height="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: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 |
<RelativeLayout |
||||||
android:layout_width="match_parent" |
android:layout_width="match_parent" |
||||||
android:layout_height="wrap_content" |
android:layout_height="wrap_content" |
||||||
android:padding="2dp" |
android:orientation="horizontal" |
||||||
android:orientation="horizontal"> |
android:padding="2dp"> |
||||||
|
|
||||||
|
|
||||||
<TextView |
<TextView |
||||||
android:id="@+id/tv_book_time" |
android:id="@+id/tv_book_time" |
||||||
android:layout_width="wrap_content" |
android:layout_width="wrap_content" |
||||||
android:layout_height="wrap_content" |
android:layout_height="wrap_content" |
||||||
android:text="time" |
android:maxLines="1" |
||||||
android:textColor="@color/textSecondary" |
android:text="time" |
||||||
android:maxLines="1" |
android:textColor="@color/textSecondary" |
||||||
android:textSize="12sp"/> |
android:textSize="12sp" /> |
||||||
|
|
||||||
<TextView |
<TextView |
||||||
android:id="@+id/tv_book_source" |
android:id="@+id/tv_book_source" |
||||||
android:layout_width="wrap_content" |
android:layout_width="wrap_content" |
||||||
android:layout_height="wrap_content" |
android:layout_height="wrap_content" |
||||||
android:layout_alignParentEnd="true" |
android:layout_alignParentEnd="true" |
||||||
android:textColor="@color/textSecondary" |
android:maxLines="1" |
||||||
android:text="book source" |
android:text="book source" |
||||||
android:maxLines="1" |
android:textColor="@color/textSecondary" |
||||||
android:textSize="12sp"/> |
android:textSize="12sp" /> |
||||||
</RelativeLayout> |
</RelativeLayout> |
||||||
|
|
||||||
|
|
||||||
<View |
<View |
||||||
android:layout_width="fill_parent" |
android:layout_width="fill_parent" |
||||||
android:layout_height="0.5dp" |
android:layout_height="0.5dp" |
||||||
android:background="@color/colorDivider"/> |
android:background="@color/colorDivider" /> |
||||||
|
|
||||||
</LinearLayout> |
</LinearLayout> |
||||||
</LinearLayout> |
</LinearLayout> |
@ -1,20 +1,22 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
android:layout_width="match_parent" |
android:layout_width="match_parent" |
||||||
android:layout_height="match_parent"> |
android:layout_height="match_parent"> |
||||||
|
|
||||||
<ProgressBar |
<xyz.fycz.myreader.widget.loading.LoadingIndicatorView |
||||||
android:id="@+id/loadding_pb" |
android:id="@+id/loading" |
||||||
android:layout_width="wrap_content" |
style="@style/LoadingIndicatorView" |
||||||
android:layout_height="wrap_content" |
android:layout_width="wrap_content" |
||||||
android:layout_centerInParent="true"/> |
android:layout_height="wrap_content" |
||||||
|
android:layout_centerInParent="true" /> |
||||||
|
|
||||||
<TextView |
<TextView |
||||||
android:paddingTop="5dp" |
android:layout_width="wrap_content" |
||||||
android:layout_below="@+id/loadding_pb" |
android:layout_height="wrap_content" |
||||||
android:layout_centerInParent="true" |
android:layout_below="@+id/loading" |
||||||
android:layout_width="wrap_content" |
android:layout_centerInParent="true" |
||||||
android:layout_height="wrap_content" |
android:paddingTop="10dp" |
||||||
android:textColor="@color/textSecondary" |
android:text="@string/loading_tip" |
||||||
android:text="@string/loading_tip"/> |
android:textSize="@dimen/text_normal_size" |
||||||
|
android:textColor="@color/textPrimary" /> |
||||||
</RelativeLayout> |
</RelativeLayout> |
Loading…
Reference in new issue