Repository: AndyJennifer/SimpleEyes
Branch: master
Commit: 6334fcbc2496
Files: 313
Total size: 655.3 KB
Directory structure:
gitextract_epk5pk4i/
├── .circleci/
│ └── config.yml
├── .gitignore
├── .idea/
│ ├── codeStyles/
│ │ ├── Project.xml
│ │ └── codeStyleConfig.xml
│ ├── dictionaries/
│ │ └── andy.xml
│ ├── encodings.xml
│ ├── gradle.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── runConfigurations.xml
│ └── vcs.xml
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── jennifer/
│ │ └── andy/
│ │ └── simpleeyes/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/
│ │ │ └── fonts/
│ │ │ └── Lobster-1.4.otf
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── jennifer/
│ │ │ └── andy/
│ │ │ └── simpleeyes/
│ │ │ ├── AndyApplication.kt
│ │ │ ├── UserPreferences.kt
│ │ │ ├── entity/
│ │ │ │ └── AndyInfo.kt
│ │ │ ├── manager/
│ │ │ │ └── ActivityManager.kt
│ │ │ ├── net/
│ │ │ │ ├── Api.kt
│ │ │ │ ├── ApiService.kt
│ │ │ │ ├── Extras.kt
│ │ │ │ └── RetrofitConfig.kt
│ │ │ ├── player/
│ │ │ │ ├── FileMediaDataSource.kt
│ │ │ │ ├── IjkMediaController.kt
│ │ │ │ ├── IjkVideoView.kt
│ │ │ │ ├── IjkVideoViewWrapper.kt
│ │ │ │ ├── MeasureHelper.kt
│ │ │ │ ├── PolicyCompat.kt
│ │ │ │ ├── event/
│ │ │ │ │ └── VideoProgressEvent.kt
│ │ │ │ ├── render/
│ │ │ │ │ ├── IRenderView.kt
│ │ │ │ │ ├── SurfaceRenderView.kt
│ │ │ │ │ └── TextureRenderView.kt
│ │ │ │ └── view/
│ │ │ │ ├── ControllerView.kt
│ │ │ │ ├── ControllerViewFactory.kt
│ │ │ │ ├── ErrorView.kt
│ │ │ │ ├── FullScreenControllerView.kt
│ │ │ │ └── TinyControllerView.kt
│ │ │ ├── router/
│ │ │ │ ├── EyesPathReplaceService.kt
│ │ │ │ ├── RouteIntercept.kt
│ │ │ │ └── SchemeFilterActivity.kt
│ │ │ ├── rx/
│ │ │ │ ├── RxBus.kt
│ │ │ │ ├── RxThreadHelper.kt
│ │ │ │ └── error/
│ │ │ │ ├── FlowableRetryDelay.kt
│ │ │ │ ├── GlobalErrorProcessor.kt
│ │ │ │ ├── GlobalErrorTransformer.kt
│ │ │ │ ├── ObservabeRetryDelay.kt
│ │ │ │ └── RetryConfig.kt
│ │ │ ├── ui/
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── author/
│ │ │ │ │ ├── AuthorTagDetailActivity.kt
│ │ │ │ │ ├── model/
│ │ │ │ │ │ └── AuthorModel.kt
│ │ │ │ │ ├── presenter/
│ │ │ │ │ │ └── AuthorTagDetailPresenter.kt
│ │ │ │ │ └── ui/
│ │ │ │ │ └── AuthorTagDetailView.kt
│ │ │ │ ├── base/
│ │ │ │ │ ├── BaseActivity.kt
│ │ │ │ │ ├── BaseAppCompatActivity.kt
│ │ │ │ │ ├── BaseAppCompatFragment.kt
│ │ │ │ │ ├── BaseFragment.kt
│ │ │ │ │ ├── BaseFragmentItemAdapter.kt
│ │ │ │ │ ├── BaseView.kt
│ │ │ │ │ ├── LoadMoreView.kt
│ │ │ │ │ ├── adapter/
│ │ │ │ │ │ └── BaseDataAdapter.kt
│ │ │ │ │ ├── model/
│ │ │ │ │ │ └── BaseModel.kt
│ │ │ │ │ └── presenter/
│ │ │ │ │ ├── BasePresenter.kt
│ │ │ │ │ └── LoadMorePresenter.kt
│ │ │ │ ├── common/
│ │ │ │ │ ├── CommonModel.kt
│ │ │ │ │ ├── CommonPresenter.kt
│ │ │ │ │ ├── CommonRecyclerActivity.kt
│ │ │ │ │ └── CommonView.kt
│ │ │ │ ├── feed/
│ │ │ │ │ ├── AllCategoryActivity.kt
│ │ │ │ │ ├── CategoryTabActivity.kt
│ │ │ │ │ ├── FeedFragment.kt
│ │ │ │ │ ├── RankListActivity.kt
│ │ │ │ │ ├── TagActivity.kt
│ │ │ │ │ ├── TagDetailInfoFragment.kt
│ │ │ │ │ ├── TopicActivity.kt
│ │ │ │ │ ├── WebViewActivity.kt
│ │ │ │ │ ├── model/
│ │ │ │ │ │ └── FeedModel.kt
│ │ │ │ │ ├── presenter/
│ │ │ │ │ │ ├── AllCategoryPresenter.kt
│ │ │ │ │ │ ├── CategoryTabPresenter.kt
│ │ │ │ │ │ ├── FeedPresenter.kt
│ │ │ │ │ │ ├── RankListPresenter.kt
│ │ │ │ │ │ ├── TagDetailInfoPresenter.kt
│ │ │ │ │ │ └── TopicPresenter.kt
│ │ │ │ │ └── view/
│ │ │ │ │ ├── AllCategoryView.kt
│ │ │ │ │ ├── CategoryTabView.kt
│ │ │ │ │ ├── FeedView.kt
│ │ │ │ │ ├── RankListView.kt
│ │ │ │ │ ├── TagDetailInfoView.kt
│ │ │ │ │ └── TopicView.kt
│ │ │ │ ├── follow/
│ │ │ │ │ ├── AllAuthorActivity.kt
│ │ │ │ │ ├── FollowFragment.kt
│ │ │ │ │ ├── model/
│ │ │ │ │ │ └── FollowModel.kt
│ │ │ │ │ ├── presenter/
│ │ │ │ │ │ ├── AllAuthorPresenter.kt
│ │ │ │ │ │ └── FollowPresenter.kt
│ │ │ │ │ └── view/
│ │ │ │ │ ├── AllAuthorView.kt
│ │ │ │ │ └── FollowView.kt
│ │ │ │ ├── home/
│ │ │ │ │ ├── DailyEliteActivity.kt
│ │ │ │ │ ├── HomeFragment.kt
│ │ │ │ │ ├── adapter/
│ │ │ │ │ │ ├── CollectionCardCoverAdapter.kt
│ │ │ │ │ │ ├── DailyEliteAdapter.kt
│ │ │ │ │ │ └── SquareCollectionAdapter.kt
│ │ │ │ │ ├── model/
│ │ │ │ │ │ └── HomeModel.kt
│ │ │ │ │ ├── presenter/
│ │ │ │ │ │ ├── DailyElitePresenter.kt
│ │ │ │ │ │ └── HomePresenter.kt
│ │ │ │ │ └── view/
│ │ │ │ │ ├── DailyEliteView.kt
│ │ │ │ │ └── HomeView.kt
│ │ │ │ ├── login/
│ │ │ │ │ └── LoginActivity.kt
│ │ │ │ ├── profile/
│ │ │ │ │ ├── CacheFragment.kt
│ │ │ │ │ ├── ProfileFragment.kt
│ │ │ │ │ ├── adapter/
│ │ │ │ │ │ └── ProfileSettingAdapter.kt
│ │ │ │ │ ├── model/
│ │ │ │ │ │ └── ProfileModel.kt
│ │ │ │ │ ├── presenter/
│ │ │ │ │ │ ├── CachePresenter.kt
│ │ │ │ │ │ └── ProfilePresenter.kt
│ │ │ │ │ └── view/
│ │ │ │ │ ├── CacheView.kt
│ │ │ │ │ └── ProfileView.kt
│ │ │ │ ├── search/
│ │ │ │ │ ├── SearchHotActivity.kt
│ │ │ │ │ ├── adapter/
│ │ │ │ │ │ ├── CollectionBriefAdapter.kt
│ │ │ │ │ │ ├── SearchHotAdapter.kt
│ │ │ │ │ │ └── SearchVideoAdapter.kt
│ │ │ │ │ ├── presenter/
│ │ │ │ │ │ └── SearchPresenter.kt
│ │ │ │ │ └── view/
│ │ │ │ │ └── SearchView.kt
│ │ │ │ ├── splash/
│ │ │ │ │ ├── LandingActivity.kt
│ │ │ │ │ ├── LocalCommonLandingFragment.kt
│ │ │ │ │ ├── SloganFragment.kt
│ │ │ │ │ ├── VideoLandingFragment.kt
│ │ │ │ │ └── adapter/
│ │ │ │ │ └── SplashVideoFragmentAdapter.kt
│ │ │ │ └── video/
│ │ │ │ ├── VideoDetailActivity.kt
│ │ │ │ ├── VideoInfoByIdActivity.kt
│ │ │ │ ├── adapter/
│ │ │ │ │ └── VideoDetailAdapter.kt
│ │ │ │ ├── model/
│ │ │ │ │ └── VideoDetailModel.kt
│ │ │ │ ├── presenter/
│ │ │ │ │ ├── VideoDetailPresenter.kt
│ │ │ │ │ └── VideoInfoByIdPresenter.kt
│ │ │ │ └── view/
│ │ │ │ ├── VideoDetailView.kt
│ │ │ │ └── VideoInfoByIdView.kt
│ │ │ ├── utils/
│ │ │ │ ├── AppUtils.kt
│ │ │ │ ├── ButterKnife.kt
│ │ │ │ ├── DensityUtils.kt
│ │ │ │ ├── IntentRouterUtils.kt
│ │ │ │ ├── KeyboardUtils.kt
│ │ │ │ ├── NetWorkUtils.kt
│ │ │ │ ├── ProcessUtils.kt
│ │ │ │ ├── ScreenUtils.kt
│ │ │ │ ├── SystemUtils.kt
│ │ │ │ ├── TimeUtils.kt
│ │ │ │ ├── ToastUtils.kt
│ │ │ │ ├── UDIDUtils.kt
│ │ │ │ ├── VideoPlayerUtils.kt
│ │ │ │ └── ext/
│ │ │ │ └── IntentEx.kt
│ │ │ └── widget/
│ │ │ ├── BottomBar.kt
│ │ │ ├── BottomItem.kt
│ │ │ ├── BottomItemLayout.kt
│ │ │ ├── CollectionOfHorizontalScrollCardView.kt
│ │ │ ├── CustomLoadMoreView.kt
│ │ │ ├── EliteImageView.kt
│ │ │ ├── FullScreenVideoView.kt
│ │ │ ├── GridItemDecoration.kt
│ │ │ ├── ItemHeaderView.kt
│ │ │ ├── SearchHotRemindView.kt
│ │ │ ├── StickyNavLayout.kt
│ │ │ ├── VerticalProgressBar.kt
│ │ │ ├── VideoDetailAuthorView.kt
│ │ │ ├── font/
│ │ │ │ ├── CustomFontTextView.kt
│ │ │ │ ├── CustomFontTypeWriterTextView.kt
│ │ │ │ ├── FontType.kt
│ │ │ │ ├── PrintSpan.kt
│ │ │ │ ├── PrintSpanGroup.kt
│ │ │ │ └── TypefaceManager.kt
│ │ │ ├── image/
│ │ │ │ ├── CenterAlignImageSpan.kt
│ │ │ │ └── imageloader/
│ │ │ │ └── FrescoImageLoader.kt
│ │ │ ├── pull/
│ │ │ │ ├── head/
│ │ │ │ │ ├── EliteHeaderView.kt
│ │ │ │ │ ├── HeaderRefreshView.kt
│ │ │ │ │ ├── HomePageHeaderView.kt
│ │ │ │ │ └── VideoDetailHeadView.kt
│ │ │ │ ├── refresh/
│ │ │ │ │ ├── LinearLayoutManagerWithSmoothScroller.kt
│ │ │ │ │ ├── PullRefreshView.kt
│ │ │ │ │ ├── PullToRefresh.kt
│ │ │ │ │ ├── PullToRefreshBase.kt
│ │ │ │ │ └── PullToRefreshRecyclerView.kt
│ │ │ │ └── zoom/
│ │ │ │ ├── PullToZoom.kt
│ │ │ │ ├── PullToZoomBase.kt
│ │ │ │ └── PullToZoomRecyclerView.kt
│ │ │ ├── state/
│ │ │ │ ├── MultipleStateView.kt
│ │ │ │ └── NetLoadingView.kt
│ │ │ ├── tab/
│ │ │ │ ├── AnimationUtils.java
│ │ │ │ ├── ShortTabLayout.java
│ │ │ │ └── TabItem.java
│ │ │ └── viewpager/
│ │ │ ├── InterceptVerticalViewPager.kt
│ │ │ └── MarginWithIndicatorViewPager.kt
│ │ └── res/
│ │ ├── anim/
│ │ │ ├── bottom_in.xml
│ │ │ ├── bottom_out.xml
│ │ │ ├── fade_in.xml
│ │ │ ├── fade_out.xml
│ │ │ ├── left_in.xml
│ │ │ ├── left_out.xml
│ │ │ ├── no_anim.xml
│ │ │ ├── right_in.xml
│ │ │ ├── right_out.xml
│ │ │ ├── scale_in.xml
│ │ │ ├── scale_out.xml
│ │ │ ├── top_in.xml
│ │ │ └── top_out.xml
│ │ ├── color/
│ │ │ └── selector_item_square_text.xml
│ │ ├── drawable/
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── seek_bar_layer.xml
│ │ │ ├── selector_checkbox_bg.xml
│ │ │ ├── selector_item_square_foreground.xml
│ │ │ ├── shape_black_border.xml
│ │ │ ├── shape_border_bottom.xml
│ │ │ ├── shape_border_bottom_top.xml
│ │ │ ├── shape_hot_search_bg.xml
│ │ │ ├── shape_indicator_selected.xml
│ │ │ ├── shape_indicator_unselected.xml
│ │ │ ├── shape_share_bg.xml
│ │ │ ├── shape_show_all_border.xml
│ │ │ ├── shape_translate_border.xml
│ │ │ ├── shape_video_detail_placeholder.xml
│ │ │ ├── shape_webview_scrollbar.xml
│ │ │ └── shape_white_border.xml
│ │ ├── layout/
│ │ │ ├── activity_all_author.xml
│ │ │ ├── activity_all_category.xml
│ │ │ ├── activity_author_tag_detail.xml
│ │ │ ├── activity_category_tab.xml
│ │ │ ├── activity_common_recyclerview.xml
│ │ │ ├── activity_daily_elite.xml
│ │ │ ├── activity_landing.xml
│ │ │ ├── activity_login.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── activity_rank.xml
│ │ │ ├── activity_tag.xml
│ │ │ ├── activity_topic.xml
│ │ │ ├── activity_video_detail.xml
│ │ │ ├── activity_videoinfo_by_id.xml
│ │ │ ├── activity_webview.xml
│ │ │ ├── dialog_light_controller.xml
│ │ │ ├── dialog_volume_controller.xml
│ │ │ ├── empty_search_word.xml
│ │ │ ├── fragment_cache.xml
│ │ │ ├── fragment_feed.xml
│ │ │ ├── fragment_follow.xml
│ │ │ ├── fragment_home.xml
│ │ │ ├── fragment_local_coomon_landing.xml
│ │ │ ├── fragment_profile.xml
│ │ │ ├── fragment_search_hot.xml
│ │ │ ├── fragment_slogan.xml
│ │ │ ├── fragment_tag_detail_info.xml
│ │ │ ├── fragment_video_landing.xml
│ │ │ ├── item_collection_brief.xml
│ │ │ ├── item_collection_card_cover.xml
│ │ │ ├── item_collection_of_horizontal_scroll_card.xml
│ │ │ ├── item_hot_search.xml
│ │ │ ├── item_profile_setting.xml
│ │ │ ├── item_square_card.xml
│ │ │ ├── item_square_collection.xml
│ │ │ ├── item_the_end.xml
│ │ │ ├── item_video_samll_card.xml
│ │ │ ├── item_video_text_card.xml
│ │ │ ├── layout_author_tag_detail_header.xml
│ │ │ ├── layout_blank_card.xml
│ │ │ ├── layout_bottom_item.xml
│ │ │ ├── layout_brife_card.xml
│ │ │ ├── layout_card_banner.xml
│ │ │ ├── layout_category_head_view.xml
│ │ │ ├── layout_category_tab_toolbar.xml
│ │ │ ├── layout_center_title_share_toolbar.xml
│ │ │ ├── layout_choiceness.xml
│ │ │ ├── layout_collection_of_horizontal_scroll_card.xml
│ │ │ ├── layout_collection_with_brief.xml
│ │ │ ├── layout_collection_with_cover.xml
│ │ │ ├── layout_common_text.xml
│ │ │ ├── layout_common_toolbar.xml
│ │ │ ├── layout_division_line.xml
│ │ │ ├── layout_follow_card.xml
│ │ │ ├── layout_horizontal_scroll_card.xml
│ │ │ ├── layout_ijk_wrapper.xml
│ │ │ ├── layout_left_title_share_toolbar.xml
│ │ │ ├── layout_load_more_view.xml
│ │ │ ├── layout_loading_message.xml
│ │ │ ├── layout_loading_view.xml
│ │ │ ├── layout_margin_with_indicator_pager.xml
│ │ │ ├── layout_media_controller_full_screen.xml
│ │ │ ├── layout_media_controller_tiny.xml
│ │ │ ├── layout_search_hot_remind_view.xml
│ │ │ ├── layout_single_text.xml
│ │ │ ├── layout_single_video.xml
│ │ │ ├── layout_square_collection.xml
│ │ │ ├── layout_video_author_head.xml
│ │ │ ├── layout_video_detail_head.xml
│ │ │ ├── layout_video_error.xml
│ │ │ ├── layout_video_small_card.xml
│ │ │ ├── refresh_category_header.xml
│ │ │ └── refresh_daily_elite_header.xml
│ │ ├── layout-v21/
│ │ │ └── layout_square_collection.xml
│ │ ├── mipmap-anydpi-v26/
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── values/
│ │ │ ├── attrs.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── ids_sticky_nav_layout.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── xml/
│ │ └── network_security_config.xml
│ └── test/
│ └── java/
│ └── com/
│ └── jennifer/
│ └── andy/
│ └── simpleeyes/
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
references:
cache_key: &cache_key
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}
jobs:
build:
docker:
- image: circleci/android:api-28-alpha
environment:
JAVA_TOOL_OPTIONS: "-Xmx1024m"
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2"
TERM: dumb
steps:
- checkout
- restore_cache:
<<: *cache_key
- run:
name: Download Dependencies
command: ./gradlew dependencies
- save_cache:
<<: *cache_key
paths:
- ~/.gradle/caches
- ~/.gradle/wrapper
- run:
name: Run JVM Tests & Lint
command: ./gradlew check
- store_artifacts:
path: app/build/reports
destination: reports
- store_test_results:
path: app/build/test-results
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
================================================
FILE: .idea/codeStyles/Project.xml
================================================
.*:id
http://schemas.android.com/apk/res/android
.*:name
http://schemas.android.com/apk/res/android
.*
http://schemas.android.com/apk/res/android
ANDROID_ATTRIBUTE_ORDER
================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
================================================
FILE: .idea/dictionaries/andy.xml
================================================
================================================
FILE: .idea/encodings.xml
================================================
================================================
FILE: .idea/gradle.xml
================================================
================================================
FILE: .idea/misc.xml
================================================
================================================
FILE: .idea/modules.xml
================================================
================================================
FILE: .idea/runConfigurations.xml
================================================
================================================
FILE: .idea/vcs.xml
================================================
================================================
FILE: LICENSE
================================================
Copyright [2019] [AndyJennifer]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# SimpleEyes
[](https://circleci.com/gh/AndyJennifer/SimpleEyes)
[](https://android-arsenal.com/api?level=21)
SimpleEyes 一款基于 Kotlin 开发的短视频项目。该项目为如下两个分支:
- [master 分支](https://github.com/AndyJennifer/SimpleEyes/tree/master) :MVP + Retrofit + Rxjava
- [jetpack 分支](https://github.com/AndyJennifer/SimpleEyes/tree/simpleeyes-jetpack):MVVM + Android Jetpack + Retrofit + RxJava
你可以根据自己的需要,选择不同的分支。
## 项目展示 ☘

## 闪光点 ✨✨
- 100% 纯 Kotlin 开发。巧妙了运用了 Kotlin 的诸多特性,如扩展函数,数据类,委托等。
- 丰富的动画与自定义View。如下拉刷新、视差动画、TextView打字动画、嵌套滑动,状态布局。
- RxJava 常用操作符的使用,并新增了全局错误处理。
- 支持视频的横竖屏切换、音量控制、亮度控制等。
- 良好的代码规范与注释。
- 良好的设计模式与架构。
- .....
## 完成的功能 💼
- 闪屏页实现
- [x] 开场短视频
- [x] 开场动画效果1、2
- 首页
- [x] 底部 Tab 切换
- [x] 顶部轮播
- [x] 下拉视差刷新,加载更多
- [x] 视频搜索
- [x] 每日精选
- [x] 登录页
- 发现
- [x] 热门、分类、作者、
- [x] 全部分类
- [x] 视频搜索
- 关注
- [x] 全部作者
- [x] 作者简介及视频
- 我的
- [ ] 我的消息
- [ ] 我的缓存
## 使用的库 💪
站在巨人的肩膀上。可以看得更远。该项目中运用了以下开源库:
- [Fresco](https://github.com/facebook/fresco)
- [ARouter](https://github.com/alibaba/ARouter)
- [Fragmentation](https://github.com/YoKeyword/Fragmentation)
- [RxJava](https://github.com/ReactiveX/RxJava)
- [Retrofit](https://github.com/square/retrofit)
- [BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper)
- [IjkPlayer](https://github.com/Bilibili/ijkplayer)
- [FlexBox-Layout](https://github.com/google/flexbox-layout)
- [Banner](https://github.com/youth5201314/banner)
- [Android KTX](https://developer.android.google.cn/kotlin/ktx)
- [Koin](https://github.com/InsertKoinIO/koin)
## 声明 📢
感谢 [开眼App](http://www.kaiyanapp.com) 提供参考,本人是豆瓣粉丝,使用了其中的 Api ,并非攻击。如构成侵权,请及时通知我删除或者修改。数据来源来自[开眼](https://www.kaiyanapp.com/) ,一切解释权归开眼所有。
## 最后
注意:此开源项目仅做学习交流使用。如用到实际项目,还需多考虑其他因素,请多多斟酌。如果你觉得该项目不错,欢迎点击 star ❤️,follow,也可以帮忙分享给你更多的朋友。你的支持与鼓励是给我继续做好该项目的最大动力。
## 联系我
- QQ:443696320
- 简书:[AndyandJennifer](https://www.jianshu.com/users/921c778fb5e1/timeline)
- 掘金:[AndyandJennifer](https://juejin.im/user/5acc1ea06fb9a028bc2e0fc1)
- Email: [andyjennifer@126.com](andyjennifer@126.com)
## License
```text
Copyright [2019] [AndyJennifer]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "com.jennifer.andy.simpleeyes"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
kotlinOptions{
jvmTarget = "1.8"
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.android.material:material:1.0.0'
//RxJava
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
//fragmentation
implementation 'me.yokeyword:fragmentationx:1.0.1'
//recyclerView
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.44'
//fresco
implementation 'com.facebook.fresco:fresco:1.11.0'
//ijk
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'
//loadingView
implementation 'com.wang.avi:library:2.1.3'
implementation 'com.youth.banner:banner:1.4.10'
//flex
implementation 'com.google.android:flexbox:0.3.2'
//pageIndicator
implementation 'com.romandanylyk:pageindicatorview:1.0.1@aar'
//ARouter
api 'com.alibaba:arouter-api:1.4.1'
kapt 'com.alibaba:arouter-compiler:1.2.2'
//LeakCanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
//ktx
implementation 'androidx.core:core-ktx:1.1.0'
//autodispose
implementation 'com.uber.autodispose:autodispose-android:1.4.0'
implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.4.0'
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/andy/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/androidTest/java/com/jennifer/andy/simpleeyes/ExampleInstrumentedTest.kt
================================================
package com.jennifer.andy.simpleeyes
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.jennifer.andy.simplemusic", appContext.packageName)
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/AndyApplication.kt
================================================
package com.jennifer.andy.simpleeyes
import android.app.Application
import com.alibaba.android.arouter.launcher.ARouter
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.squareup.leakcanary.LeakCanary
/**
* Author: andy.xwt
* Date: 2017/10/15 09:27
* Description:
*/
class AndyApplication : Application() {
companion object {
lateinit var INSTANCE: Application
}
override fun onCreate() {
super.onCreate()
INSTANCE = this
initARoute()
initFresco()
initLeakCanary()
}
/**
* 初始化路由操作
*/
private fun initARoute() {
if (BuildConfig.DEBUG) {
ARouter.openLog()
ARouter.openDebug()
}
ARouter.init(this)
}
/**
* 初始化Fresco,打开压缩
*/
private fun initFresco() {
val config = ImagePipelineConfig.newBuilder(this)
.setDownsampleEnabled(true)
.build()
Fresco.initialize(this, config)
}
/**
* 初始化LeakCanary,检测内存泄露
*/
private fun initLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return
}
LeakCanary.install(this)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/UserPreferences.kt
================================================
package com.jennifer.andy.simpleeyes
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
/**
* Author: andy.xwt
* Date: 2018/4/9 09:34
* Description:用户配置信息
*/
object UserPreferences {
private const val NAME = "andy."
private const val KEY_IS_USER_LOGIN = "is_user_login"
private const val KEY_IS_FIRST_LOGIN = "is_first_login"
private const val KEY_IS_SHOW_USER_ANIM = "key_is_show_user_anim"
/**
* 保存用户是否登录配置
*/
fun saveUserIsLogin(isUserLogin: Boolean) {
getSharedPreferences().edit {
putBoolean(KEY_IS_USER_LOGIN, isUserLogin)
}
}
/**
* 是否像用户展示过动画
*/
fun saveShowUserAnim(isUserLogin: Boolean) {
getSharedPreferences().edit {
putBoolean(KEY_IS_SHOW_USER_ANIM, isUserLogin)
}
}
/**
* 保存用户是否是第一次登录
*/
fun saveUserIsFirstLogin(isFirstLogin: Boolean) {
getSharedPreferences().edit {
putBoolean(KEY_IS_FIRST_LOGIN, isFirstLogin)
}
}
/**
* 获取用户是否登录
*/
fun getUserIsLogin() = getSharedPreferences().getBoolean(KEY_IS_USER_LOGIN, false)
/**
* 是否像用户展示过动画
*/
fun getShowUserAnim() = getSharedPreferences().getBoolean(KEY_IS_SHOW_USER_ANIM, false);
/**
* 获取用户是否是第一次登录
*/
fun getUserIsFirstLogin() = getSharedPreferences().getBoolean(KEY_IS_FIRST_LOGIN, true)
private fun getSharedPreferences(): SharedPreferences {
return AndyApplication.INSTANCE.getSharedPreferences(NAME, Context.MODE_PRIVATE)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/entity/AndyInfo.kt
================================================
package com.jennifer.andy.simpleeyes.entity
import java.io.Serializable
/**
* Author: andy.xwt
* Date: 2017/10/19 19:00
* Description: 页面数据信息,主要是视频信息
*/
data class AndyInfo(var count: Int,
var total: Int,
var nextPageUrl: String?,
var date: Long,
var nextPublishTime: Long,
var topIssue: TopIssue,
var refreshCount: Int,
var lastStartId: Int,
var itemList: MutableList) : Serializable
data class JenniferInfo(var nextPageUrl: String?,
var date: Long,
var nextPublishTime: Long,
var topIssue: TopIssue,
var refreshCount: Int,
var lastStartId: Int,
var issueList: MutableList) : Serializable
data class Content(var type: String,
var data: ContentBean,
var id: String,
var date: Long,
var tag: String) : Serializable
data class ContentBean(var dataType: String,
var header: Header?,
var follow: FollowInfo?,
var content: Content?,
var itemList: MutableList,
var id: String,
var title: String,
var description: String,
var library: String,
var tags: MutableList,
var consumption: ConsumptionBean,
var image: String,
var resourceType: String,
var slogan: String,
var provider: ProviderBean,
var category: String,
var author: AuthorBean,
var cover: CoverBean,
var playUrl: String,
var thumbPlayUrl: String,
var duration: Int,
var webUrl: WebUrlBean,
var releaseTime: Long,
var playInfo: MutableList,
var campaign: String,
var waterMarks: String,
var type: String?,
var titlePgc: String,
var descriptionPgc: String,
var remark: String,
var idx: Int,
var shareAdTrack: String,
var favoriteAdTrack: String,
var webAdTrack: String,
var date: Long,
val label: Label?,
var labelList: MutableList<*>,
var descriptionEditor: String,
var isCollected: Boolean,
var isPlayed: Boolean,
var subtitles: MutableList<*>,
var lastViewTime: String,
var playlists: String,
var text: String,
var icon: String,
var iconType: String,
var height: Int,
var src: Int,
var actionUrl: String?) : Serializable
data class Header(var id: String,
var title: String?,
var subTitle: String?,
var subTitleFont: String,
var textAlign: String?,
var font: String,
var cover: String,
var label: Any,
var actionUrl: String?,
var labelList: MutableList<*>,
var icon: String?,
var iconType: String,
var description: String?,
var follow: FollowInfo?,
var time: Long?) : Serializable
data class Label(var text: String,
var card: String,
var detail: String) : Serializable
data class TagsBean(var id: Int,
var name: String,
var actionUrl: String,
var adTrack: String) : Serializable
data class ConsumptionBean(var collectionCount: String,
var shareCount: String,
var replyCount: String) : Serializable
data class TopIssue(var type: String,
var data: TopIssueBean,
var tag: String) : Serializable
data class TopIssueBean(var dataType: String,
var count: Int,
var itemList: MutableList) : Serializable
data class ProviderBean(var name: String,
var alias: String,
var icon: String) : Serializable
data class AuthorBean(var id: String,
var icon: String,
var name: String,
var description: String,
var link: String,
var latestReleaseTime: Long,
var videoNum: Int,
var adTrack: String,
var follow: FollowInfo,
var shield: ShieldBean,
var approvedNotReadyVideoCount: Int,
var isIfPgc: Boolean = false) : Serializable
data class ShieldBean(
var itemType: String,
var itemId: Int,
var isShielded: Boolean) : Serializable
data class CoverBean(var feed: String,
var detail: String,
var blurred: String,
var sharing: String,
var homepage: String) : Serializable
data class WebUrlBean(var raw: String,
var forWeibo: String) : Serializable
data class PlayInfoBean(var height: Int,
var width: Int,
var name: String,
var type: String,
var url: String,
var urlList: MutableList) : Serializable
data class UrlListBean(var name: String,
var url: String,
var size: Double) : Serializable
data class Category(var categoryInfo: CategoryInfo,
var tabInfo: TabInfo)
data class CategoryInfo(var dataType: String,
var id: String,
var name: String,
var description: String,
var headerImage: String,
var actionUrl: String,
var followInfo: FollowInfo)
data class Tab(var tabInfo: TabInfo,
var pgcInfo: PgcInfo) : Serializable
data class TabInfo(var tabList: MutableList,
var defaultIdx: Int) : Serializable
data class TabDetailInfo(var id: Int,
var name: String,
var apiUrl: String) : Serializable
data class FollowInfo(var itemType: String,
var itemId: String,
var followed: Boolean) : Serializable
data class PgcInfo(var dataType: String,
var id: Int,
var icon: String,
var name: String,
var brief: String,
var description: String,
var actionUrl: String,
var followCount: Int,
var shareCount: Int,
var videoCount: Int,
var collectCount: Int,
var followInfo: FollowInfo) : Serializable
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/manager/ActivityManager.kt
================================================
package com.jennifer.andy.simpleeyes.manager
import android.app.Activity
/**
* Author: andy.xwt
* Date: 2017/8/14 00:15
* Description:Activity管理类
*/
class ActivityManager private constructor() {
private val sActivityList = ArrayList()
companion object {
fun getInstance(): ActivityManager {
return BaseAppManagerHolder.instance
}
}
private object BaseAppManagerHolder {
val instance = ActivityManager()
}
/**
* 当前activity的个数
*/
val mActivitySize: Int get() = sActivityList.size
val mForwardActivity: Activity? @Synchronized get() = if (mActivitySize > 0) sActivityList[mActivitySize - 1] else null
/**
* 添加相应activity
*/
@Synchronized
fun removeActivity(activity: Activity) {
if (activity in sActivityList) sActivityList.remove(activity)
}
/**
* 添加相应的activity
*/
@Synchronized
fun addActivity(activity: Activity) {
sActivityList.add(activity)
}
/**
* 清除栈内activity
*/
@Synchronized
fun clear() {
var i = sActivityList.size - 1
while (i > -1) {
val activity = sActivityList[i]
removeActivity(activity)
activity.finish()
i = sActivityList.size
i--
}
}
/**
* 清除栈顶activity
*/
@Synchronized
fun clearToTop() {
var i = sActivityList.size - 2
while (i > -1) {
val activity = sActivityList[i]
removeActivity(activity)
activity.finish()
i = sActivityList.size - 1
i--
}
}
/**
* 获取最上层的Activity
*/
@Synchronized
fun getTopActivity(): Activity? {
return if (sActivityList.isNotEmpty()) {
sActivityList[sActivityList.size - 1]
} else {
null
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/Api.kt
================================================
package com.jennifer.andy.simpleeyes.net
/**
* Author: andy.xwt
* Date: 2017/10/15 09:54
* Description:
*/
object Api {
/**
* 主域名
*/
val BASE_URL: String get() = "http://baobab.kaiyanapp.com/"
/**
* 获取默认Service
*/
fun getDefault() = RetrofitConfig.getDefaultService()
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/ApiService.kt
================================================
package com.jennifer.andy.simpleeyes.net
import com.jennifer.andy.simpleeyes.entity.*
import io.reactivex.Observable
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.*
/**
* Author: andy.xwt
* Date: 2017/10/10 22:46
* Description:
*/
interface ApiService {
///////////////////////////////////////////////////////////////////////////
// 首页相关
///////////////////////////////////////////////////////////////////////////
/**
* 首页
*/
@GET("api/v4/tabs/selected")
fun getHomeInfo(): Observable
/**
* 获取热门关键词
*/
@GET("api/v3/queries/hot")
fun getHotWord(): Observable>
/**
* 关键词搜索
*/
@GET("api/v1/search")
fun searchVideoByWord(@Query("query") word: String): Observable
/**
* 每日精选旁的日历显示
*/
@GET("api/v3/issueNavigationList")
fun getIssueNaviGationList(): Observable
///////////////////////////////////////////////////////////////////////////
// 发现相关
///////////////////////////////////////////////////////////////////////////
/**
* 发现
*/
@GET("api/v4/discovery")
fun getDiscoveryTab(): Observable
/**
* 获取全部分类信息
*/
@GET("api/v4/categories/all")
fun getAllCategoriesInfo(): Observable
/**
* 获取排行榜tab信息
*/
@GET("api/v4/rankList")
fun getRankListTab(): Observable
/**
* 获取专题信息
*/
@GET("api/v3/specialTopics")
fun getTopicInfo(): Observable
/**
* 获取tag信息
* @param tagId tagId
* @param strategy tag模式
*/
@GET("api/v3/tag/videos")
fun getTagInfo(@Query("tagId") tagId: String, @Query("strategy") strategy: String): Observable
/**
* 获取种类下tab详细信息
*/
@GET("api/v4/categories/detail/tab")
fun getCategoryTabInfo(@Query("id") id: String): Observable
/**
* 获取种类下tab下列表集合
*/
@GET("api/v4/categories/detail/index")
fun getCategroyTabListItemInfo(@Query("id") id: String): Observable
///////////////////////////////////////////////////////////////////////////
// 关注相关
///////////////////////////////////////////////////////////////////////////
/**
* 关注
*/
@GET("api/v4/tabs/follow")
fun getFollowInfo(): Observable
/**
* 全部作者
*/
@GET("api/v4/pgcs/all")
fun getAllAuthor(): Observable
/**
* 作者详细信息
*/
@GET("api/v4/pgcs/detail/tab")
fun getAuthorTagDetail(@Query("id") id: String): Observable
///////////////////////////////////////////////////////////////////////////
// 公共接口
///////////////////////////////////////////////////////////////////////////
/**
* 根据url,获取数据
* @url tab请求地址
*/
@GET
fun getDataInfoFromUrl(@Url url: String?): Observable
/**
* 根据url,获取更多信息
* @param url 下一页请求地址
*/
@GET
fun getMoreAndyInfo(@Url url: String?): Observable
/**
* 根据url,获取更多信息
* @param url 下一页请求地址
*/
@GET
fun getMoreJenniferInfo(@Url url: String?): Observable
///////////////////////////////////////////////////////////////////////////
// 视频相关
///////////////////////////////////////////////////////////////////////////
/**
* 根据视频id,获取相关信息
*/
@GET("api/v2/video/{id}")
fun getVideoInfoById(@Path("id") id: String): Observable
/**
* 获取相关视频信息
* @id 视频id
*/
@GET("api/v4/video/related")
fun getRelatedVideo(@Query("id") id: String): Observable
/**
* 每日编辑精选
*/
@GET("api/v2/feed?num=3")
fun getDailyElite(): Observable
/**
* 下载视频
*/
@Streaming
@GET
fun downloadVideo(@Url url: String): Call
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/Extras.kt
================================================
package com.jennifer.andy.simpleeyes.net
/**
* Author: andy.xwt
* Date: 2017/10/31 14:16
* Description:
*/
object Extras {
/**
* 更新参数
*/
const val UPDATE_PARAMS = "update_params"
const val VIDEO_INFO = "video_info"
const val VIDEO_INFO_INDEX = "video_info_index"
const val VIDEO_LIST_INFO = "video_list_info"
const val SLOGAN_ENGLISH = "slogan_english"
const val SLOGAN_CHINESE = "slogan_chinese"
const val API_URL = "api_url"
const val ID = "id"
const val TAB_INDEX = "tab_index"
const val TITLE = "title"
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/RetrofitConfig.kt
================================================
package com.jennifer.andy.simpleeyes.net
import com.google.gson.GsonBuilder
import com.jennifer.andy.simpleeyes.AndyApplication
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.util.concurrent.TimeUnit
/**
* Author: andy.xwt
* Date: 2017/10/10 22:41
* Description:
*/
class RetrofitConfig private constructor() {
private lateinit var mRetrofit: Retrofit
private lateinit var mHttpLoggingInterceptor: HttpLoggingInterceptor
private lateinit var mRequestInterceptor: Interceptor
private lateinit var mOkHttpClient: OkHttpClient
private lateinit var mCache: Cache
companion object {
private const val READ_TIME_OUT = 5000L// 读取超过时间 单位:毫秒
private const val WRITE_TIME_OUT = 5000L//写超过时间 单位:毫秒
private const val FILE_CACHE_SIZE = 1024 * 1024 * 100L//缓存大小100Mb
private val Instance: RetrofitConfig by lazy { RetrofitConfig() }
/**
* 获取默认请求接口
*/
@JvmStatic
fun getDefaultService(): ApiService {
return Instance.apiService
}
}
private lateinit var apiService: ApiService
init {
initRequestInterceptor()
initLoggingInterceptor()
initCachePathAndSize()
initOkHttpClient()
initRetrofit()
}
/**
* 初始化请求拦截,添加缓存头,与全局请求参数
*/
private fun initRequestInterceptor() {
mRequestInterceptor = Interceptor { chain ->
//注意全局请求参数都是死的
val urlBuilder = chain.request().url()
.newBuilder()
.setEncodedQueryParameter("udid", "d0f6190461864a3a978bdbcb3fe9b48709f1f390")
.setEncodedQueryParameter("vc", "225")
.setEncodedQueryParameter("vn", "3.12.0")
.setEncodedQueryParameter("deviceModel", "Redmi%204")
.setEncodedQueryParameter("first_channel", "eyepetizer_xiaomi_market")
.setEncodedQueryParameter("last_channel", "eyepetizer_xiaomi_market")
.setEncodedQueryParameter("system_version_code", "23")
val request = chain.request().newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("Cookie", "ky_auth=;sdk=23")
.addHeader("model", "Android")
.url(urlBuilder.build())
.build()
chain.proceed(request)
}
}
/**
* 初始化日志拦截
*/
private fun initLoggingInterceptor() {
mHttpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
/**
* 配置缓存大小与缓存地址
*/
private fun initCachePathAndSize() {
val cacheFile = File(AndyApplication.INSTANCE.cacheDir, "cache")
mCache = Cache(cacheFile, FILE_CACHE_SIZE)
}
/**
* 配置okHttp
*/
private fun initOkHttpClient() {
mOkHttpClient = OkHttpClient.Builder()
.readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
.writeTimeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS)
.cache(mCache)
.addInterceptor(mRequestInterceptor)
.addInterceptor(mHttpLoggingInterceptor)
.build()
}
/**
* 配置retrofit
*/
private fun initRetrofit() {
val gson = GsonBuilder()
.setDateFormat("yyyy-MM-dd hh:mm:ss")
.create()
mRetrofit = Retrofit.Builder()
.client(mOkHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(Api.BASE_URL)
.build()
apiService = mRetrofit.create(ApiService::class.java)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/FileMediaDataSource.kt
================================================
package com.jennifer.andy.simpleeyes.player
import tv.danmaku.ijk.media.player.misc.IMediaDataSource
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
/**
* Author: andy.xwt
* Date: 2020/3/10 13:29
* Description:
*/
class FileMediaDataSource(file: File) : IMediaDataSource {
private var mFile: RandomAccessFile = RandomAccessFile(file, "r")
var mFileSize = mFile.length()
@Throws(IOException::class)
override fun readAt(position: Long, buffer: ByteArray?, offset: Int, size: Int): Int {
if (mFile.filePointer != position) mFile.seek(position)
return if (size == 0) 0 else mFile.read(buffer, 0, size)
}
@Throws(IOException::class)
override fun getSize(): Long {
return mFileSize
}
@Throws(IOException::class)
override fun close() {
mFileSize = 0
mFile.close()
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkMediaController.kt
================================================
package com.jennifer.andy.simpleeyes.player
import android.app.Activity
import android.content.Context
import android.graphics.PixelFormat
import android.media.AudioManager
import android.util.Log
import android.view.*
import android.view.View.OnLayoutChangeListener
import android.view.View.OnTouchListener
import android.widget.FrameLayout
import android.widget.MediaController.MediaPlayerControl
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.ContentBean
import com.jennifer.andy.simpleeyes.player.event.VideoProgressEvent
import com.jennifer.andy.simpleeyes.player.view.ControllerView
import com.jennifer.andy.simpleeyes.player.view.ControllerViewFactory
/**
* Author: andy.xwt
* Date: 2020/3/10 17:38
* Description:IjkMediaController 用于视频播放的UI,内部原理是通过window进行展示,其中window的位置与大小
* 是根据[mAnchor]的位置动态调整的。
*
*/
class IjkMediaController(context: Context) : FrameLayout(context) {
private lateinit var player: MediaPlayerControl
private lateinit var windowManager: WindowManager
private lateinit var window: Window
private lateinit var decorLayoutParams: WindowManager.LayoutParams
private lateinit var decorView: View
private lateinit var mAnchor: View//锚点view,用于更新window的位置
var isShowing = false//当前window是否展示
var currentVideoInfo: ContentBean? = null
var totalCount = 0
var currentIndex = 0
private var showMode1 = ControllerViewFactory.TINY_MODE //当前window展示的类型,默认情况下是小视图
var controllerListener: ControllerListener? = null
var controllerView: ControllerView? = null
private val mControllerViewFactory = ControllerViewFactory()
companion object {
private const val WINDOW_TIME_OUT = 3500L //默认window消失时间 3.5秒
}
init {
initFloatingWindowLayout()
initFloatingWindowConfig()
}
/**
* 初始化window布局
*/
private fun initFloatingWindowLayout() {
decorLayoutParams = WindowManager.LayoutParams().apply {
gravity = Gravity.TOP or Gravity.LEFT
height = LayoutParams.WRAP_CONTENT
x = 0
format = PixelFormat.TRANSLUCENT
type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
flags = flags or (WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
token = null
windowAnimations = 0
}
}
/**
* 初始化window参数
*/
private fun initFloatingWindowConfig() {
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
window = PolicyCompat().createPhoneWindow(context).apply {
setWindowManager(windowManager, null, null)
requestFeature(Window.FEATURE_NO_TITLE)
setContentView(this@IjkMediaController)
setBackgroundDrawableResource(R.color.transparent)
volumeControlStream = AudioManager.STREAM_MUSIC
}
decorView = window.decorView
decorView.setOnTouchListener(mTouchListener)
isFocusable = true
isFocusableInTouchMode = true
descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS
requestFocus()
}
/**
* 初始化数据
*
* @param currentIndex 当前视频角标
* @param totalCount 视频集合总数
* @param currentVideoInfo 当前视频信息
*/
fun initData(currentIndex: Int, totalCount: Int, currentVideoInfo: ContentBean?) {
this.currentIndex = currentIndex
this.totalCount = totalCount
this.currentVideoInfo = currentVideoInfo
}
/**
* 当锚点布局发生改变时,动态更新window的位置,高度与宽度
*/
private fun updateFloatingWindowLayout() {
val anchorPos = IntArray(2)
mAnchor.getLocationOnScreen(anchorPos)
//重新测量
decorView.measure(MeasureSpec.makeMeasureSpec(mAnchor.width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(mAnchor.height, MeasureSpec.AT_MOST))
decorLayoutParams.apply {
width = mAnchor.width
x = anchorPos[0]
y = anchorPos[1]
height = mAnchor.height
}
}
fun setMediaPlayer(player: MediaPlayerControl) {
this.player = player
switchControllerView(showMode1)
}
/**
* 当锚点view布局发生改变的时,重新设置当前window的布局参数
*/
private val mLayoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateFloatingWindowLayout()
if (isShowing) windowManager.updateViewLayout(decorView, decorLayoutParams)
}
/**
* 将控制层view与锚点的关联
*/
fun setAnchorView(view: View) {
mAnchor = view
mAnchor.addOnLayoutChangeListener(mLayoutChangeListener)
updateFloatingWindowLayout()
}
///////////////////////////////////////////////////////////////////////////
// Window显示相关
///////////////////////////////////////////////////////////////////////////
/**
* 触摸监听,如果当前window正在显示,再次按下走默认时间内该window会消失
*/
private val mTouchListener = OnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
if (isShowing) {
removeCallbacks(mFadeOut)
postDelayed(mFadeOut, WINDOW_TIME_OUT)
}
}
false
}
/**
* 隐藏控制层view
*/
private val mFadeOut = Runnable {
if (isShowing) hide()
}
/**
* 第一次显示,不显示控制层
*/
fun firstShow() {
if (!isShowing && controllerView != null) {
controllerView?.display()
}
}
/**
* 将控制器显示在屏幕上,当到达过期时间时会自动消失。
*
* @param timeout 过期时间(毫秒) 如果设置为0,那么会直到调用hide方法才会消失
*/
@JvmOverloads
fun show(timeout: Long = WINDOW_TIME_OUT) {
if (!isShowing) {
windowManager.addView(decorView, decorLayoutParams)
isShowing = true
}
if (timeout != 0L) {
removeCallbacks(mFadeOut)
postDelayed(mFadeOut, timeout.toLong())
}
controllerView?.display()
controllerListener?.onShowController(true)
}
/**
* 隐藏window
*/
fun hide() {
try {
removeCallbacks(mFadeOut)
isShowing = false
controllerListener?.onShowController(false)
windowManager.removeView(decorView)
} catch (ex: IllegalArgumentException) {
Log.w("MediaController", "already removed")
}
}
/**
* 一直显示window
*/
fun showAllTheTime() {
show(3600000)
removeCallbacks(mFadeOut)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val keyCode = event.keyCode
val uniqueDown = (event.repeatCount == 0 && event.action == KeyEvent.ACTION_DOWN)
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
if (uniqueDown) {
hide()
//如果传入的是activity直接退出
if (context is Activity) (context as Activity).finish()
}
return true
}
return super.dispatchKeyEvent(event)
}
///////////////////////////////////////////////////////////////////////////
// Window显示内容相关
///////////////////////////////////////////////////////////////////////////
/**
* 根据显示模式切换展示的控制UI
*
* @param showMode
*/
fun switchControllerView(showMode: Int) {
showMode1 = showMode
removeAllViews()
controllerView = mControllerViewFactory.create(showMode, context)
controllerView?.initControllerAndData(player, this, currentVideoInfo)
controllerView?.display()
addView(controllerView)
}
/**
* 显示错误布局
*/
fun showErrorView() {
if (!isShowing) {
controllerView?.showErrorView()
windowManager.addView(decorView, decorLayoutParams)
isShowing = true
}
}
fun updateProgressAndTime(videoProgressEvent: VideoProgressEvent?) {
controllerView?.updateProgressAndTime(videoProgressEvent!!)
}
fun resetType() {
controllerView?.showContent()
}
///////////////////////////////////////////////////////////////////////////
// 数据相关
///////////////////////////////////////////////////////////////////////////
/**
* 判断是否拥有上一个视频
*/
fun isHavePreVideo() = totalCount > 0 && currentIndex > 0
/**
* 是否拥有下一个视频
*/
fun isHaveNextVideo() = totalCount > 0 && currentIndex < totalCount - 1
///////////////////////////////////////////////////////////////////////////
// 回调监听
///////////////////////////////////////////////////////////////////////////
interface ControllerListener {
//退出点击
fun onBackClick()
//上一页点击
fun onPreClick()
//下一页点击
fun onNextClick()
//全屏点击
fun onFullScreenClick()
//退出全屏
fun onTinyScreenClick()
//错误界面点击
fun onErrorViewClick()
//是否显示控制层
fun onShowController(isShowController: Boolean)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkVideoView.kt
================================================
package com.jennifer.andy.simpleeyes.player
import android.annotation.TargetApi
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import android.widget.MediaController.MediaPlayerControl
import com.jennifer.andy.simpleeyes.player.event.VideoProgressEvent
import com.jennifer.andy.simpleeyes.player.render.IRenderView
import com.jennifer.andy.simpleeyes.player.render.IRenderView.IRenderCallback
import com.jennifer.andy.simpleeyes.player.render.IRenderView.ISurfaceHolder
import com.jennifer.andy.simpleeyes.player.render.SurfaceRenderView
import com.jennifer.andy.simpleeyes.player.render.TextureRenderView
import com.jennifer.andy.simpleeyes.player.view.ControllerViewFactory
import com.jennifer.andy.simpleeyes.rx.RxBus
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import tv.danmaku.ijk.media.player.misc.IMediaDataSource
import java.io.File
import java.io.IOException
import java.util.*
import java.util.concurrent.TimeUnit
/**
* Author: andy.xwt
* Date: 2020/3/12 23:03
* Description:
*/
class IjkVideoView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: FrameLayout(context, attrs, defStyleAttr), MediaPlayerControl {
private var mUri: Uri? = null
private var mHeaders: Map? = null
private var mSurfaceHolder: ISurfaceHolder? = null
private var mMediaPlayer: IMediaPlayer? = null
private var mVideoWidth = 0
private var mVideoHeight = 0
private var mSurfaceWidth = 0
private var mSurfaceHeight = 0
private var mRenderView: IRenderView? = null
private var mCurrentRender = RENDER_NONE
private var mRenderUIView: View? = null
private var mVideoRotationDegree = 0
private var mVideoSarNum = 0
private var mVideoSarDen = 0
private var mCurrentAspectRatio = allAspectRatio[0]
private var mMediaController: IjkMediaController? = null
private var mOnCompletionListener: IMediaPlayer.OnCompletionListener? = null
private var mOnPreparedListener: IMediaPlayer.OnPreparedListener? = null
private var mOnErrorListener: IMediaPlayer.OnErrorListener? = null
private var mOnInfoListener: IMediaPlayer.OnInfoListener? = null
private var mCurrentBufferPercentage = 0
private var mSeekWhenPrepared = 0
private val mCanPause = true
private val mCanSeekBack = true
private val mCanSeekForward = true
private lateinit var mAppContext: Context
private var mCurrentState = STATE_IDLE
private var mTargetState = STATE_IDLE
var screenState = ControllerViewFactory.TINY_MODE
private val mSHCallback: IRenderCallback = object : IRenderCallback {
override fun onSurfaceChanged(holder: ISurfaceHolder, format: Int, w: Int, h: Int) {
if (holder.renderView !== mRenderView) {
Log.e(TAG, "onSurfaceChanged: unmatched render callback\n")
return
}
mSurfaceWidth = w
mSurfaceHeight = h
val isValidState = mTargetState == STATE_PLAYING
val hasValidSize = !mRenderView!!.shouldWaitForResize() || mVideoWidth == w && mVideoHeight == h
if (mMediaPlayer != null && isValidState && hasValidSize) {
if (mSeekWhenPrepared != 0) {
seekTo(mSeekWhenPrepared)
}
start()
}
}
override fun onSurfaceCreated(holder: ISurfaceHolder, width: Int, height: Int) {
if (holder.renderView !== mRenderView) {
Log.e(TAG, "onSurfaceCreated: unmatched render callback\n")
return
}
mSurfaceHolder = holder
if (mMediaPlayer != null) bindSurfaceHolder(mMediaPlayer, holder) else openVideo()
}
override fun onSurfaceDestroyed(holder: ISurfaceHolder) {
if (holder.renderView !== mRenderView) {
Log.e(TAG, "onSurfaceDestroyed: unmatched render callback\n")
return
}
// after we return from this we can't use the surface any more
mSurfaceHolder = null
// REMOVED: if (mMediaController != null) mMediaController.hide();
// REMOVED: release(true);
releaseWithoutStop()
}
}
companion object {
private const val STATE_ERROR = -1
private const val STATE_IDLE = 0
private const val STATE_PREPARING = 1
private const val STATE_PREPARED = 2
private const val STATE_PLAYING = 3
private const val STATE_PAUSED = 4
private const val STATE_PLAYBACK_COMPLETED = 5
private val allAspectRatio = intArrayOf(
IRenderView.AR_ASPECT_FIT_PARENT,
IRenderView.AR_ASPECT_FILL_PARENT,
IRenderView.AR_ASPECT_WRAP_CONTENT,
IRenderView.AR_16_9_FIT_PARENT,
IRenderView.AR_4_3_FIT_PARENT)
const val RENDER_NONE = 0
const val RENDER_SURFACE_VIEW = 1
const val RENDER_TEXTURE_VIEW = 2
private const val TAG = "IjkVideoView"
}
init {
initVideoView(context)
}
fun toggleAspectRatio(currentAspectRatio: Int): Int {
mRenderView?.setAspectRatio(currentAspectRatio)
mCurrentAspectRatio = currentAspectRatio
return mCurrentAspectRatio
}
private fun initVideoView(context: Context) {
mAppContext = context.applicationContext
initRenders()
//初始化宽高
mVideoWidth = 0
mVideoHeight = 0
//获取焦点
isFocusable = true
isFocusableInTouchMode = true
requestFocus()
//初始化状态
mCurrentState = STATE_IDLE
mTargetState = STATE_IDLE
//设置背景
background = ColorDrawable(Color.BLACK)
}
/**
* 设置渲染Render
* 4.0 及以上版本使用 TextureView
* 4.0 版本以下使用 SurfaceView
*/
private fun initRenders() {
mCurrentRender = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { //4.0以上
RENDER_TEXTURE_VIEW
} else {
RENDER_SURFACE_VIEW
}
setRender(mCurrentRender)
}
/**
* 设置渲染Render
*/
private fun setRender(render: Int) {
when (render) {
RENDER_NONE -> setRenderView(null)
RENDER_TEXTURE_VIEW -> {
//TextureView
val renderView = TextureRenderView(context)
if (mMediaPlayer != null) {
renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer)
renderView.setVideoSize(mMediaPlayer!!.videoWidth, mMediaPlayer!!.videoHeight)
renderView.setVideoSampleAspectRatio(mMediaPlayer!!.videoSarNum, mMediaPlayer!!.videoSarDen)
renderView.setAspectRatio(mCurrentAspectRatio)
}
setRenderView(renderView)
}
RENDER_SURFACE_VIEW -> {
//surfaceView
val renderView = SurfaceRenderView(context)
setRenderView(renderView)
}
else -> Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render))
}
}
/**
* 设置渲染界面,初始化当前的渲染界面参数,并设置渲染回调等初始化操作
*/
private fun setRenderView(renderView: IRenderView?) {
if (mRenderView != null) {
if (mMediaPlayer != null)
mMediaPlayer?.setDisplay(null)
val renderUIView = mRenderView?.getView()
mRenderView?.removeRenderCallback(mSHCallback)
mRenderView = null
removeView(renderUIView)
}
if (renderView == null) return
mRenderView = renderView
renderView.setAspectRatio(mCurrentAspectRatio) //设置适配比例
if (mVideoWidth > 0 && mVideoHeight > 0)
renderView.setVideoSize(mVideoWidth, mVideoHeight) //设置视频的宽高
if (mVideoSarNum > 0 && mVideoSarDen > 0) //设置采样比例
renderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen)
mRenderUIView = mRenderView?.getView().apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
Gravity.CENTER)
}
addView(mRenderUIView)
mRenderView?.addRenderCallback(mSHCallback) //添加回调
mRenderView?.setVideoRotation(mVideoRotationDegree) //设置视频旋转
}
/**
* 设置视频播放路径
*
* @param path 视频路径
*/
fun setVideoPath(path: String?) {
setVideoURI(Uri.parse(path))
}
/**
* 设置视频URI
*
* @param uri 视频URI
*/
fun setVideoURI(uri: Uri) {
setVideoURI(uri, null)
}
/**
* 设置带header 的 视频URI
*
*
* 注意,默认情况下允许跨域重定向,
* 但可以通过以“android allow cross domain redirect”为键,
* 以“0”或“1”为值的headers,以禁止或允许跨域重定向。
*
* @param uri 视频URI.
* @param headers URI请求需要的header
*/
private fun setVideoURI(uri: Uri, headers: Map?) {
mUri = uri
mHeaders = headers
mSeekWhenPrepared = 0
openVideo()
requestLayout()
invalidate()
}
fun stopPlayback() {
mMediaPlayer?.let {
mMediaPlayer?.stop()
mMediaPlayer?.release()
mMediaPlayer = null
mCurrentState = STATE_IDLE
mTargetState = STATE_IDLE
mMediaController?.let {
mMediaController?.hide()
mMediaController = null
}
val am = mAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.abandonAudioFocus(null)
cancelProgressRunnable()
}
}
/**
* 打开视频
*/
@TargetApi(Build.VERSION_CODES.M)
private fun openVideo() {
if (mUri == null || mSurfaceHolder == null) { // not ready for playback just yet, will try again later
return
}
// we shouldn't clear the target state, because somebody might have
// called start() previously
release(false)
val am = mAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
try {
mMediaPlayer = createPlayer()
mMediaPlayer?.setOnPreparedListener(mPreparedListener)
mMediaPlayer?.setOnVideoSizeChangedListener(mSizeChangedListener)
mMediaPlayer?.setOnCompletionListener(mCompletionListener)
mMediaPlayer?.setOnErrorListener(mErrorListener)
mMediaPlayer?.setOnInfoListener(mInfoListener)
mMediaPlayer?.setOnBufferingUpdateListener(mBufferingUpdateListener)
mMediaPlayer?.setOnSeekCompleteListener(mSeekCompleteListener)
mCurrentBufferPercentage = 0
val scheme = mUri?.scheme
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && (TextUtils.isEmpty(scheme)
|| scheme.equals("file", ignoreCase = true))) {
val dataSource: IMediaDataSource = FileMediaDataSource(File(mUri.toString()))
mMediaPlayer?.setDataSource(dataSource)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMediaPlayer?.setDataSource(mAppContext, mUri, mHeaders)
} else {
mMediaPlayer?.dataSource = mUri.toString()
}
bindSurfaceHolder(mMediaPlayer, mSurfaceHolder)
mMediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC)
mMediaPlayer?.setScreenOnWhilePlaying(true)
mMediaPlayer?.prepareAsync()
mCurrentState = STATE_PREPARING
attachMediaController()
} catch (ex: IOException) {
Log.w(TAG, "Unable to open content: $mUri", ex)
mCurrentState = STATE_ERROR
mTargetState = STATE_ERROR
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0)
} catch (ex: IllegalArgumentException) {
Log.w(TAG, "Unable to open content: $mUri", ex)
mCurrentState = STATE_ERROR
mTargetState = STATE_ERROR
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0)
} finally { // REMOVED: mPendingSubtitleTracks.clear();
}
}
fun setMediaController(controller: IjkMediaController?) {
mMediaController = controller
attachMediaController()
}
fun getMediaController(): IjkMediaController? {
return mMediaController
}
private fun attachMediaController() {
if (mMediaPlayer != null && mMediaController != null) {
mMediaController?.setMediaPlayer(this)
val anchorView = if (this.parent is View) this.parent as View else this
mMediaController?.setAnchorView(anchorView)
mMediaController?.isEnabled = isInPlaybackState()
}
}
var mSizeChangedListener = IMediaPlayer.OnVideoSizeChangedListener { mp, _, _, _, _ ->
mVideoWidth = mp.videoWidth
mVideoHeight = mp.videoHeight
mVideoSarNum = mp.videoSarNum
mVideoSarDen = mp.videoSarDen
if (mVideoWidth != 0 && mVideoHeight != 0) {
mRenderView?.let {
mRenderView?.setVideoSize(mVideoWidth, mVideoHeight)
mRenderView?.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen)
}
// REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
requestLayout()
}
}
/**
* 视频播放准备完成监听
*/
var mPreparedListener = IMediaPlayer.OnPreparedListener { mp ->
mCurrentState = STATE_PREPARED
if (mOnPreparedListener != null) {
mOnPreparedListener!!.onPrepared(mMediaPlayer)
}
if (mMediaController != null) {
mMediaController!!.isEnabled = true
}
mVideoWidth = mp.videoWidth
mVideoHeight = mp.videoHeight
val seekToPosition = mSeekWhenPrepared // mSeekWhenPrepared may be changed after seekTo() call
if (seekToPosition != 0) {
seekTo(seekToPosition)
}
if (mVideoWidth != 0 && mVideoHeight != 0) {
mRenderView?.let {
mRenderView?.setVideoSize(mVideoWidth, mVideoHeight)
mRenderView?.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen)
if (!mRenderView!!.shouldWaitForResize() || mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
// We didn't actually change the size (it was already at the size
// we need), so we won't get a "surface changed" callback, so
// start the video here instead of in the callback.
if (mTargetState == STATE_PLAYING) {
start()
mMediaController?.firstShow()
} else if (!isPlaying && (seekToPosition != 0 || currentPosition > 0)) {
// Show the media controls when we're paused into a video and make 'em stick.
mMediaController?.show(0)
}
}
}
} else { // We don't know the video size yet, but should start anyway.
// The video size might be reported to us later.
if (mTargetState == STATE_PLAYING) {
start()
}
}
}
private val mCompletionListener = IMediaPlayer.OnCompletionListener {
mCurrentState = STATE_PLAYBACK_COMPLETED
mTargetState = STATE_PLAYBACK_COMPLETED
mMediaController?.hide()
mOnCompletionListener?.onCompletion(mMediaPlayer)
}
private val mInfoListener = IMediaPlayer.OnInfoListener { mp, arg1, arg2 ->
mOnInfoListener?.onInfo(mp, arg1, arg2)
when (arg1) {
IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING -> Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:")
IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START -> Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:")
IMediaPlayer.MEDIA_INFO_BUFFERING_START -> Log.d(TAG, "MEDIA_INFO_BUFFERING_START:")
IMediaPlayer.MEDIA_INFO_BUFFERING_END -> Log.d(TAG, "MEDIA_INFO_BUFFERING_END:")
IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH -> Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: $arg2")
IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING -> Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:")
IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE -> Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:")
IMediaPlayer.MEDIA_INFO_METADATA_UPDATE -> Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:")
IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE -> Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:")
IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT -> Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:")
IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED -> {
mVideoRotationDegree = arg2
Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: $arg2")
if (mRenderView != null) mRenderView!!.setVideoRotation(arg2)
}
IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START -> Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:")
}
true
}
private val mErrorListener = IMediaPlayer.OnErrorListener { mp, framework_err, impl_err ->
Log.d(TAG, "Error: $framework_err,$impl_err")
mCurrentState = STATE_ERROR
mTargetState = STATE_ERROR
/* If an error handler has been supplied, use it and finish. */if (mOnErrorListener != null) {
if (mOnErrorListener!!.onError(mMediaPlayer, framework_err, impl_err)) {
return@OnErrorListener true
}
}
true
}
private val mBufferingUpdateListener = IMediaPlayer.OnBufferingUpdateListener { _, percent ->
mCurrentBufferPercentage = percent
}
private val mSeekCompleteListener = IMediaPlayer.OnSeekCompleteListener { }
/**
* Register a callback to be invoked when the media file
* is loaded and ready to go.
*
* @param l The callback that will be run
*/
fun setOnPreparedListener(l: IMediaPlayer.OnPreparedListener?) {
mOnPreparedListener = l
}
/**
* Register a callback to be invoked when the end of a media file
* has been reached during playback.
*
* @param l The callback that will be run
*/
fun setOnCompletionListener(l: IMediaPlayer.OnCompletionListener?) {
mOnCompletionListener = l
}
/**
* Register a callback to be invoked when an error occurs
* during playback or setup. If no listener is specified,
* or if the listener returned false, VideoView will inform
* the user of any errors.
*
* @param l The callback that will be run
*/
fun setOnErrorListener(l: IMediaPlayer.OnErrorListener?) {
mOnErrorListener = l
}
/**
* Register a callback to be invoked when an informational event
* occurs during playback or setup.
*
* @param l The callback that will be run
*/
fun setOnInfoListener(l: IMediaPlayer.OnInfoListener?) {
mOnInfoListener = l
}
// REMOVED: mSHCallback
private fun bindSurfaceHolder(mp: IMediaPlayer?, holder: ISurfaceHolder?) {
if (mp == null) return
if (holder == null) {
mp.setDisplay(null)
return
}
holder.bindToMediaPlayer(mp)
}
fun releaseWithoutStop() {
if (mMediaPlayer != null) mMediaPlayer!!.setDisplay(null)
}
/*
* release the media player in any state
*/
fun release(clearTargetState: Boolean) {
if (mMediaPlayer != null) {
mMediaPlayer!!.reset()
mMediaPlayer!!.release()
mMediaPlayer = null
mMediaController!!.hide()
if (clearTargetState) {
mTargetState = STATE_IDLE
}
setRenderView(null)
initRenders()
val am = mAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.abandonAudioFocus(null)
cancelProgressRunnable()
}
}
override fun onTrackballEvent(ev: MotionEvent?): Boolean {
if (isInPlaybackState() && mMediaController != null) {
toggleMediaControlsVisible()
}
return false
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
val isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && keyCode != KeyEvent.KEYCODE_MENU && keyCode != KeyEvent.KEYCODE_CALL && keyCode != KeyEvent.KEYCODE_ENDCALL
if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
if (mMediaPlayer!!.isPlaying) {
pause()
mMediaController!!.show()
} else {
start()
mMediaController!!.hide()
}
return true
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
if (!mMediaPlayer!!.isPlaying) {
start()
mMediaController!!.hide()
}
return true
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
if (mMediaPlayer!!.isPlaying) {
pause()
mMediaController!!.show()
}
return true
} else {
toggleMediaControlsVisible()
}
}
return super.onKeyDown(keyCode, event)
}
fun toggleMediaControlsVisible() {
if (mMediaController!!.isShowing) {
mMediaController?.hide()
} else {
mMediaController?.show()
}
}
private var mDisposable: Disposable? = null
/**
* 进度与时间更新线程
*/
private fun startProgressRunnable() {
if (mDisposable == null || mDisposable!!.isDisposed) {
mDisposable = Observable.interval(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
val position = currentPosition
val duration = duration
if (duration >= 0 && bufferPercentage > 0) {
val progress = 1000L * position / duration
val secondProgress = bufferPercentage * 10
//发送进度
val event = VideoProgressEvent(duration, position, progress.toInt(), secondProgress)
//这里在更新进度条时,有可能在切换控制器,所以这里要排除空的情况
if (mMediaController != null) {
mMediaController!!.updateProgressAndTime(event)
}
RxBus.post(event)
}
}
}
}
/**
* 取消进度与时间更新线程
*/
private fun cancelProgressRunnable() {
if (mDisposable != null && !mDisposable!!.isDisposed) {
mDisposable?.dispose()
mDisposable = null
}
}
override fun start() {
if (isInPlaybackState()) {
mMediaPlayer?.start()
mCurrentState = STATE_PLAYING
startProgressRunnable()
}
mTargetState = STATE_PLAYING
}
override fun pause() {
if (isInPlaybackState()) {
if (mMediaPlayer!!.isPlaying) {
mMediaPlayer?.pause()
mCurrentState = STATE_PAUSED
cancelProgressRunnable()
}
}
mTargetState = STATE_PAUSED
}
fun suspend() {
release(false)
cancelProgressRunnable()
}
fun resume() {
openVideo()
}
override fun getDuration(): Int {
return if (isInPlaybackState()) {
mMediaPlayer?.duration!!.toInt()
} else -1
}
override fun getCurrentPosition(): Int {
return if (isInPlaybackState()) {
mMediaPlayer?.currentPosition!!.toInt()
} else 0
}
override fun seekTo(msec: Int) {
mSeekWhenPrepared = if (isInPlaybackState()) {
mMediaPlayer?.seekTo(msec.toLong())
0
} else {
msec
}
}
override fun isPlaying(): Boolean {
return isInPlaybackState() && mMediaPlayer!!.isPlaying
}
override fun getBufferPercentage(): Int {
return if (mMediaPlayer != null) {
mCurrentBufferPercentage
} else 0
}
fun isInPlaybackState(): Boolean {
return mMediaPlayer != null
&& mCurrentState != STATE_ERROR
&& mCurrentState != STATE_IDLE
&& mCurrentState != STATE_PREPARING
}
override fun canPause() = mCanPause
override fun canSeekBackward() = mCanSeekBack
override fun canSeekForward() = mCanSeekForward
override fun getAudioSessionId(): Int {
return 0
}
private fun createPlayer(): IMediaPlayer? {
if (mUri != null) {
return IjkMediaPlayer().apply {
IjkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG)
setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0)
setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0)
setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32.toLong())
setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1)
setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0)
setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0)
setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48)
}
}
return null
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkVideoViewWrapper.kt
================================================
package com.jennifer.andy.simpleeyes.player
import android.app.Dialog
import android.content.Context
import android.content.pm.ActivityInfo
import android.graphics.drawable.ColorDrawable
import android.media.AudioManager
import android.provider.Settings
import android.provider.Settings.SettingNotFoundException
import android.util.AttributeSet
import android.view.*
import android.widget.FrameLayout
import android.widget.ProgressBar
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.player.view.ControllerViewFactory
import com.jennifer.andy.simpleeyes.utils.*
import tv.danmaku.ijk.media.player.IMediaPlayer
import kotlin.math.abs
/**
* Author: andy.xwt
* Date: 2020/3/14 13:53
* Description: IjkVideoViewWrapper 包含了对声音亮度的控制,同时代理了[IjkVideoView]的相关方法
*/
class IjkVideoViewWrapper @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: FrameLayout(context, attrs, defStyleAttr), GestureDetector.OnGestureListener {
private val mIjkVideoView: IjkVideoView
private val mPlaceImage: SimpleDraweeView
private val mCenterProgress: ProgressBar
private val mGestureDetector: GestureDetector
private val mAudioManager: AudioManager
private var mLightDialog: Dialog? = null
private var mLightProgress: ProgressBar? = null
private var mVolumeDialog: Dialog? = null
private var mVolumeProgress: ProgressBar? = null
private var isShowVolume = false
private var isShowLight = false
private var isShowPosition = false
private val mScreenHeight: Int
private var mParent: ViewGroup? = null
companion object {
private const val MIN_SCROLL = 3
}
init {
LayoutInflater.from(context).inflate(R.layout.layout_ijk_wrapper, this, true)
mGestureDetector = GestureDetector(context, this)
mAudioManager = getContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
mScreenHeight = getContext().getScreenHeight()
mIjkVideoView = findViewById(R.id.video_view)
mPlaceImage = findViewById(R.id.iv_place_image)
mCenterProgress = findViewById(R.id.progress)
}
fun setPlaceImageUrl(url: String?) {
togglePlaceImage(true)
mPlaceImage.setImageURI(url)
}
fun togglePlaceImage(visibility: Boolean) {
mPlaceImage.visibility = if (visibility) View.VISIBLE else View.GONE
mCenterProgress.visibility = if (visibility) View.VISIBLE else View.GONE
}
fun hidePlaceImage() {
mPlaceImage.handler.postDelayed({ togglePlaceImage(false) }, 500)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val relValue = mGestureDetector.onTouchEvent(event)
val action = event.action
if (action == MotionEvent.ACTION_UP) { //当手指抬起的时候
onUp()
} else if (action == MotionEvent.ACTION_CANCEL) {
onCancel()
}
return relValue
}
override fun onDown(e: MotionEvent?): Boolean {
// 单击,触摸屏按下时立刻触发
return true
}
override fun onShowPress(e: MotionEvent?) {
// 短按,触摸屏按下后片刻后抬起,会触发这个手势,如果迅速抬起则不会
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
// 抬起,手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势)
if (mIjkVideoView.isInPlaybackState() && mIjkVideoView.getMediaController() != null && (!isShowVolume || !isShowLight || isShowPosition)) {
mIjkVideoView.toggleMediaControlsVisible()
}
return true
}
// 滚动,触摸屏按下后移动
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
if (mIjkVideoView.screenState == ControllerViewFactory.FULL_SCREEN_MODE) { //判断是否是全屏
val downX = e1.x
val actuallyDy = e2.y - e1.y
val actuallyDx = e2.x - e1.x
val absDy = abs(actuallyDy)
val absDx = abs(actuallyDx)
//左右移动
if (absDx >= MIN_SCROLL && absDx > absDy) {
showMoveToPositionDialog()
}
//上下移动
if (abs(distanceY) >= MIN_SCROLL && absDy > absDx) {
if (downX <= mScreenHeight * 0.5f) { //左边,改变声音
showVolumeDialog(distanceY)
} else { //右边改变亮度
showLightDialog(distanceY)
}
}
}
return true
}
/**
* 移动到相应位置
*/
private fun showMoveToPositionDialog() {
// TODO: 2018/2/26 xwt 移动到相应的位置
isShowPosition = true
}
private fun onCancel() {
onUp()
}
private fun onUp() {
dismissLightDialog()
dismissVolumeDialog()
}
//手势识别中不需要使用的方法
override fun onLongPress(e: MotionEvent?) {}
//手势识别中不需要使用的方法
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
return false
}
/**
* 显示声音控制对话框
*/
private fun showVolumeDialog(deltaY: Float) {
isShowVolume = true
//记录滑动时候当前的声音
var currentVideoVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
if (deltaY < 0) { //向下滑动
currentVideoVolume--
} else { //向下滑动
currentVideoVolume++
}
//设置声音
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVideoVolume, 0)
if (mVolumeDialog == null) {
val view = LayoutInflater.from(context).inflate(R.layout.dialog_volume_controller, null)
mVolumeProgress = view.findViewById(R.id.pb_volume_progress)
mVolumeDialog = createDialogWithView(view, Gravity.START or Gravity.CENTER_VERTICAL)
}
if (!mVolumeDialog!!.isShowing) {
mVolumeDialog!!.show()
}
//设置进度条
val nextVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
var volumePercent = (nextVolume * 1f / maxVolume * 100f).toInt()
if (volumePercent > 100) {
volumePercent = 100
} else if (volumePercent < 0) {
volumePercent = 0
}
mVolumeProgress!!.progress = volumePercent
}
/**
* 隐藏声音对话框
*/
private fun dismissVolumeDialog() {
if (mVolumeDialog != null) {
mVolumeDialog!!.dismiss()
isShowVolume = false
}
}
/**
* 显示控制亮度对话框
*/
private fun showLightDialog(deltaY: Float) {
isShowLight = true
var screenBrightness = 0f
//记录滑动前的亮度
val lp = getWindow(context)!!.attributes
if (lp.screenBrightness < 0) {
try {
screenBrightness = Settings.System.getInt(context.contentResolver, Settings.System.SCREEN_BRIGHTNESS).toFloat()
} catch (e: SettingNotFoundException) {
e.printStackTrace()
}
} else {
screenBrightness = lp.screenBrightness
}
if (deltaY < 0) { //向下滑动
screenBrightness -= 0.03f
} else { //向下滑动
screenBrightness += 0.03f
}
when {
screenBrightness >= 1 -> {
lp.screenBrightness = 1f
}
screenBrightness < 0 -> {
lp.screenBrightness = 0.1f
}
else -> {
lp.screenBrightness = screenBrightness
}
}
getWindow(context)!!.attributes = lp
//设置亮度百分比
if (mLightProgress == null) {
val view = LayoutInflater.from(context).inflate(R.layout.dialog_light_controller, null)
mLightProgress = view.findViewById(R.id.pb_light_progress)
mLightDialog = createDialogWithView(view, Gravity.END or Gravity.CENTER_VERTICAL)
}
if (!mLightDialog!!.isShowing) {
mLightDialog?.show()
}
val lightPercent = (screenBrightness * 100f).toInt()
mLightProgress?.progress = lightPercent
}
private fun dismissLightDialog() {
mLightDialog?.let {
mLightDialog!!.dismiss()
isShowLight = false
}
}
/**
* 根据View与位置创建dialog
*
* @param localView 内容布局
* @param gravity 位置
*/
fun createDialogWithView(localView: View?, gravity: Int): Dialog {
return Dialog(context, R.style.VideoProgress).apply {
setContentView(localView)
window.apply {
addFlags(Window.FEATURE_ACTION_BAR)
addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
setLayout(-2, -2)
setBackgroundDrawable(ColorDrawable())
val localLayoutParams = attributes
localLayoutParams.gravity = gravity
attributes = localLayoutParams
}
}
}
/**
* 进入全屏
*/
fun enterFullScreen() {
hideActionBar(context)
getActivity(context)!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
val contentView = getActivity(context)!!.findViewById(Window.ID_ANDROID_CONTENT)
mParent = parent as ViewGroup
mParent!!.removeView(this)
mIjkVideoView.screenState = ControllerViewFactory.FULL_SCREEN_MODE
val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
contentView.addView(this, params)
}
/**
* 退出全屏
*/
fun exitFullScreen() {
showActionBar(context)
val contentView = getActivity(context)!!.findViewById(Window.ID_ANDROID_CONTENT)
getActivity(context)!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
contentView.removeView(this)
mIjkVideoView.screenState = ControllerViewFactory.TINY_MODE
val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
mParent!!.addView(this, params)
}
///////////////////////////////////////////////////////////////////////////
// 代理IjkVideoView方法
///////////////////////////////////////////////////////////////////////////
fun setVideoPath(playUrl: String) {
mIjkVideoView.setVideoPath(playUrl)
}
fun start() {
mIjkVideoView.start()
}
fun setMediaController(controller: IjkMediaController?) {
mIjkVideoView.setMediaController(controller)
}
fun toggleAspectRatio(currentAspectRatio: Int) {
mIjkVideoView.toggleAspectRatio(currentAspectRatio)
}
fun setOnPreparedListener(onPreparedListener: IMediaPlayer.OnPreparedListener) {
mIjkVideoView.setOnPreparedListener(onPreparedListener)
}
fun setOnErrorListener(onErrorListener: IMediaPlayer.OnErrorListener) {
mIjkVideoView.setOnErrorListener(onErrorListener)
}
fun setOnCompletionListener(onCompletionListener: IMediaPlayer.OnCompletionListener) {
mIjkVideoView.setOnCompletionListener(onCompletionListener)
}
fun stopPlayback() {
mIjkVideoView.stopPlayback()
}
fun isPlaying(): Boolean {
return mIjkVideoView.isPlaying
}
fun pause() {
mIjkVideoView.pause()
}
fun release(clearTargetState: Boolean) {
mIjkVideoView.release(clearTargetState)
}
fun showErrorView(): Boolean {
mCenterProgress.handler.postDelayed({
mCenterProgress.visibility = View.GONE
mIjkVideoView.getMediaController()!!.showErrorView()
}, 1000)
return false
}
fun resetType() {
mIjkVideoView.getMediaController()?.resetType()
}
fun setDragging(dragging: Boolean) {
mIjkVideoView.getMediaController()!!.controllerView!!.isDragging = dragging
}
fun isDragging(): Boolean {
return mIjkVideoView.getMediaController()!!.controllerView!!.isDragging
}
fun showControllerAllTheTime() {
mIjkVideoView.getMediaController()?.showAllTheTime()
}
fun getDuration(): Int {
return mIjkVideoView.duration
}
fun showController() {
mIjkVideoView.getMediaController()!!.show()
}
fun seekTo(msec: Int) {
mIjkVideoView.seekTo(msec)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/MeasureHelper.kt
================================================
package com.jennifer.andy.simpleeyes.player
import android.view.View
import com.jennifer.andy.simpleeyes.player.render.IRenderView
import java.lang.ref.WeakReference
/**
* Author: andy.xwt
* Date: 2020/3/9 15:09
* Description:
*/
/**
* 测量帮助类,根据宽高比及方向,获得测量后的宽高
*/
class MeasureHelper(view: View) {
private val mWeakView: WeakReference?
private var mVideoWidth = 0//视频布局的宽度
private var mVideoHeight = 0//视频布局的高度
private var mVideoSarNum = 0 //实际视频的宽
private var mVideoSarDen = 0//实际视频的高
private var mVideoRotationDegree = 0 //屏幕旋转度数
var measuredWidth = 0//测量后的宽
var measuredHeight = 0 //测量后的高
private var mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT //宽高比例
val view: View? get() = mWeakView?.get()
init {
mWeakView = WeakReference(view)
}
fun setVideoSize(videoWidth: Int, videoHeight: Int) {
mVideoWidth = videoWidth
mVideoHeight = videoHeight
}
fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int) {
mVideoSarNum = videoSarNum
mVideoSarDen = videoSarDen
}
fun setVideoRotation(videoRotationDegree: Int) {
mVideoRotationDegree = videoRotationDegree
}
/**
* 当 View.onMeasure(int, int) 时调用,用于输出视频校正后的宽高
*
* @param widthMeasureSpec 宽度测量规则
* @param heightMeasureSpec 高度测量规则
*/
fun doMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var widthMeasureSpec = widthMeasureSpec
var heightMeasureSpec = heightMeasureSpec
if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
//如果屏幕为横屏,将宽高进行对调
val tempSpec = widthMeasureSpec
widthMeasureSpec = heightMeasureSpec
heightMeasureSpec = tempSpec
}
//获取父布局宽高
var width = View.getDefaultSize(mVideoWidth, widthMeasureSpec)
var height = View.getDefaultSize(mVideoHeight, heightMeasureSpec)
if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) {
//如果宽高比例是匹配父布局,宽高不变
width = widthMeasureSpec
height = heightMeasureSpec
}
//设置的视频宽高大于0时
else if (mVideoWidth > 0 && mVideoHeight > 0) {
val widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec)
val widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec)
val heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec)
val heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec)
//当前控件测量模式为包裹模式时,根据视频比例与渲染比例,计算出视频显示的宽高比
if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) {
val specAspectRatio = widthSpecSize.toFloat() / heightSpecSize.toFloat()
var displayAspectRatio: Float
when (mCurrentAspectRatio) {
IRenderView.AR_16_9_FIT_PARENT -> {
displayAspectRatio = 16.0f / 9.0f
if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
displayAspectRatio = 1.0f / displayAspectRatio
}
IRenderView.AR_4_3_FIT_PARENT -> {
displayAspectRatio = 4.0f / 3.0f
if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
displayAspectRatio = 1.0f / displayAspectRatio
}
IRenderView.AR_ASPECT_FIT_PARENT,
IRenderView.AR_ASPECT_FILL_PARENT,
IRenderView.AR_ASPECT_WRAP_CONTENT -> {
displayAspectRatio = mVideoWidth.toFloat() / mVideoHeight.toFloat()
if (mVideoSarNum > 0 && mVideoSarDen > 0)
displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen
}
else -> {
displayAspectRatio = mVideoWidth.toFloat() / mVideoHeight.toFloat()
if (mVideoSarNum > 0 && mVideoSarDen > 0)
displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen
}
}
val shouldBeWider = displayAspectRatio > specAspectRatio
when (mCurrentAspectRatio) {
//以下三种模式,以大的为固定边,小的边按比例缩放
IRenderView.AR_ASPECT_FIT_PARENT,
IRenderView.AR_16_9_FIT_PARENT,
IRenderView.AR_4_3_FIT_PARENT ->
if (shouldBeWider) {
//高度偏小,则宽度不变,对高进行校正
width = widthSpecSize
height = (width / displayAspectRatio).toInt()
} else {
//宽度偏小,则高度不变,对宽进行校正
height = heightSpecSize
width = (height * displayAspectRatio).toInt()
}
//填充模式下,以小的为固定边,大的边按比例缩放
IRenderView.AR_ASPECT_FILL_PARENT ->
if (shouldBeWider) {
//高度偏小,则高度不变,对宽进行校正
height = heightSpecSize
width = (height * displayAspectRatio).toInt()
} else {
//宽度偏小,则宽度不变,对高进行校正
width = widthSpecSize
height = (width / displayAspectRatio).toInt()
}
//包裹模式,以视频采样的宽高为准。
IRenderView.AR_ASPECT_WRAP_CONTENT ->
if (shouldBeWider) {
//高度偏小,将视频宽度与父布局宽度进行比较,取最小。并重新计算高度
width = mVideoWidth.coerceAtMost(widthSpecSize)
height = (width / displayAspectRatio).toInt()
} else {
//宽度偏小,将视频高度与父布局高度进行比较,取最小。重新计算宽度
height = mVideoHeight.coerceAtMost(heightSpecSize)
width = (height * displayAspectRatio).toInt()
}
//默认情况下
else -> if (shouldBeWider) {
width = mVideoWidth.coerceAtMost(widthSpecSize)
height = (width / displayAspectRatio).toInt()
} else {
height = mVideoHeight.coerceAtMost(heightSpecSize)
width = (height * displayAspectRatio).toInt()
}
}
}
//视频宽高为准确模式下,以小的为固定边,大的边按比例缩放
else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
width = widthSpecSize
height = heightSpecSize
if (mVideoWidth * height < width * mVideoHeight) {// videoWidth/videoHeight < width / height
//宽度过大,重新调整宽度
width = height * mVideoWidth / mVideoHeight
} else if (mVideoWidth * height > width * mVideoHeight) {
//高度过大,重新调整高度
height = width * mVideoHeight / mVideoWidth
}
}
// 只有宽在准确模式下,宽度以实际为准,高度不能超过父布局
else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
width = widthSpecSize
height = width * mVideoHeight / mVideoWidth
if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
height = heightSpecSize
}
}
//只有高在准确模式模式下,高度以实际为准,宽度不能超过父布局
else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
height = heightSpecSize
width = height * mVideoWidth / mVideoHeight
if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
width = widthSpecSize
}
}
// 当宽高都不固定时,使用实际的视频宽高
else {
width = mVideoWidth
height = mVideoHeight
if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
//太高,重新校正宽高
height = heightSpecSize
width = height * mVideoWidth / mVideoHeight
}
if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
//太宽,重新校正宽高
width = widthSpecSize
height = width * mVideoHeight / mVideoWidth
}
}
} else { // no size yet, just adopt the given spec sizes
}
measuredWidth = width
measuredHeight = height
}
fun setAspectRatio(aspectRatio: Int) {
mCurrentAspectRatio = aspectRatio
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/PolicyCompat.kt
================================================
package com.jennifer.andy.simpleeyes.player
import android.content.Context
import android.view.Window
/**
* Author: andy.xwt
* Date: 2020/3/9 15:07
* Description:
*/
private const val PHONE_WINDOW_CLASS_NAME = "com.android.internal.policy.PhoneWindow"
private const val POLICY_MANAGER_CLASS_NAME = "com.android.internal.policy.PolicyManager"
class PolicyCompat {
/**
* 通过反射拿不到phoneWindow,当该方法失败时,通过[makeNewWindow]
*/
fun createPhoneWindow(context: Context): Window {
return try {
val clazz = Class.forName(PHONE_WINDOW_CLASS_NAME)
val c = clazz.getConstructor(Context::class.java)
c.newInstance(context) as Window
} catch (e: Exception) {
makeNewWindow(context)
}
}
private fun makeNewWindow(context: Context): Window {
return try {
val clazz = Class.forName(POLICY_MANAGER_CLASS_NAME)
val m = clazz.getMethod("makeNewWindow", Context::class.java)
m.invoke(null, context) as Window
} catch (e: Exception) {
throw RuntimeException(e.message)
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/event/VideoProgressEvent.kt
================================================
package com.jennifer.andy.simpleeyes.player.event
/**
* Author: andy.xwt
* Date: 2020/3/9 10:02 PM
* Description: 视频播放进度事件
*/
data class VideoProgressEvent(
var duration: Int,//总时间
var currentPosition: Int,//当前播放时间
var progress: Int,//第一进度(播放进度)
var secondaryProgress: Int//第二进度(下载进度)
)
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/render/IRenderView.kt
================================================
package com.jennifer.andy.simpleeyes.player.render
import android.graphics.SurfaceTexture
import android.view.Surface
import android.view.SurfaceHolder
import android.view.View
import tv.danmaku.ijk.media.player.IMediaPlayer
/**
* Author: andy.xwt
* Date: 2020/3/9 22:44
* Description:
*/
interface IRenderView {
companion object {
const val AR_ASPECT_FIT_PARENT = 0 // 适应父布局模式
const val AR_ASPECT_FILL_PARENT = 1 //填充父布局模式
const val AR_ASPECT_WRAP_CONTENT = 2 //包裹模式
const val AR_MATCH_PARENT = 3 //匹配父布局模式
const val AR_16_9_FIT_PARENT = 4 //16:9适配屏幕
const val AR_4_3_FIT_PARENT = 5 //4:3适配屏幕
}
/**
* 获取当前view
*/
fun getView(): View?
/**
* 是否需要等待重新测量
*/
fun shouldWaitForResize(): Boolean
/**
* 设置视屏的宽高
*/
fun setVideoSize(videoWidth: Int, videoHeight: Int)
/**
* 设置视频采样方向比例
*/
fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int)
/**
* 设置视频旋转角度
*/
fun setVideoRotation(degree: Int)
/**
* 设置方向比例
*/
fun setAspectRatio(aspectRatio: Int)
/**
* 设置渲染回调
*/
fun addRenderCallback(callback: IRenderCallback)
/**
* 移除渲染回调
*/
fun removeRenderCallback(callback: IRenderCallback)
/**
* surfaceHolder管理相关接口
*/
interface ISurfaceHolder {
/**
* 绑定当前媒体播放器
*/
fun bindToMediaPlayer(mp: IMediaPlayer?)
/**
* 获取当前渲染的view
*/
val renderView: IRenderView
/**
* 获取当前surfaceHolder
*/
val surfaceHolder: SurfaceHolder?
/**
* 打开surface
*/
fun openSurface(): Surface?
/**
* 获取SurfaceTexture
*/
val surfaceTexture: SurfaceTexture?
}
interface IRenderCallback {
fun onSurfaceCreated(holder: ISurfaceHolder, width: Int, height: Int)
fun onSurfaceChanged(holder: ISurfaceHolder, format: Int, width: Int, height: Int)
fun onSurfaceDestroyed(holder: ISurfaceHolder)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/render/SurfaceRenderView.kt
================================================
package com.jennifer.andy.simpleeyes.player.render
import android.annotation.TargetApi
import android.content.Context
import android.graphics.SurfaceTexture
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.jennifer.andy.simpleeyes.player.MeasureHelper
import com.jennifer.andy.simpleeyes.player.render.IRenderView.IRenderCallback
import com.jennifer.andy.simpleeyes.player.render.IRenderView.ISurfaceHolder
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.ISurfaceTextureHolder
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap
/**
* Author: andy.xwt
* Date: 2020/3/9 22:47
* Description:surfaceView渲染界面
*/
class SurfaceRenderView : SurfaceView, IRenderView {
private lateinit var mMeasureHelper: MeasureHelper
constructor(context: Context) : super(context) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initView(context)
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
initView(context)
}
private fun initView(context: Context) {
mMeasureHelper = MeasureHelper(this)
mSurfaceCallback = SurfaceCallback(this)
holder.addCallback(mSurfaceCallback)
holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL)
}
override fun getView() = this
override fun shouldWaitForResize() = true
//--------------------
// Layout & Measure
//--------------------
override fun setVideoSize(videoWidth: Int, videoHeight: Int) {
if (videoWidth > 0 && videoHeight > 0) {
mMeasureHelper.setVideoSize(videoWidth, videoHeight)
holder.setFixedSize(videoWidth, videoHeight)
requestLayout()
}
}
override fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int) {
if (videoSarNum > 0 && videoSarDen > 0) {
mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen)
requestLayout()
}
}
override fun setVideoRotation(degree: Int) {
Log.e("", "SurfaceView doesn't support rotation ($degree)!\n")
}
override fun setAspectRatio(aspectRatio: Int) {
mMeasureHelper.setAspectRatio(aspectRatio)
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(mMeasureHelper.measuredWidth, mMeasureHelper.measuredHeight)
}
//--------------------
// SurfaceViewHolder
//--------------------
private class InternalSurfaceHolder(private val surfaceView: SurfaceRenderView,
override val surfaceHolder: SurfaceHolder?) : ISurfaceHolder {
override fun bindToMediaPlayer(mp: IMediaPlayer?) {
if (mp != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
mp is ISurfaceTextureHolder) {
val textureHolder = mp as ISurfaceTextureHolder
textureHolder.surfaceTexture = null
}
mp.setDisplay(surfaceHolder)
}
}
override val renderView: IRenderView
get() = surfaceView
override val surfaceTexture: SurfaceTexture?
get() = null
override fun openSurface(): Surface? {
return surfaceHolder?.surface
}
}
//-------------------------
// SurfaceHolder.Callback
//-------------------------
override fun addRenderCallback(callback: IRenderCallback) {
mSurfaceCallback.addRenderCallback(callback)
}
override fun removeRenderCallback(callback: IRenderCallback) {
mSurfaceCallback.removeRenderCallback(callback)
}
private lateinit var mSurfaceCallback: SurfaceCallback
private class SurfaceCallback(surfaceView: SurfaceRenderView) : SurfaceHolder.Callback {
private var mSurfaceHolder: SurfaceHolder? = null
private var mIsFormatChanged = false
private var mFormat = 0
private var mWidth = 0
private var mHeight = 0
private val mWeakSurfaceView: WeakReference = WeakReference(surfaceView)
private val mRenderCallbackMap: MutableMap = ConcurrentHashMap()
fun addRenderCallback(callback: IRenderCallback) {
mRenderCallbackMap[callback] = callback
var surfaceHolder: ISurfaceHolder? = null
if (mSurfaceHolder != null) {
if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight)
}
if (mIsFormatChanged) {
if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight)
}
}
fun removeRenderCallback(callback: IRenderCallback) {
mRenderCallbackMap.remove(callback)
}
override fun surfaceCreated(holder: SurfaceHolder) {
mSurfaceHolder = holder
mIsFormatChanged = false
mFormat = 0
mWidth = 0
mHeight = 0
//调用监听
val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
for (renderCallback in mRenderCallbackMap.keys) {
renderCallback.onSurfaceCreated(surfaceHolder, 0, 0)
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
mSurfaceHolder = null
mIsFormatChanged = false
mFormat = 0
mWidth = 0
mHeight = 0
val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
for (renderCallback in mRenderCallbackMap.keys) {
renderCallback.onSurfaceDestroyed(surfaceHolder)
}
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int,
width: Int, height: Int) {
mSurfaceHolder = holder
mIsFormatChanged = true
mFormat = format
mWidth = width
mHeight = height
// mMeasureHelper.setVideoSize(width, height);
val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
for (renderCallback in mRenderCallbackMap.keys) {
renderCallback.onSurfaceChanged(surfaceHolder, format, width, height)
}
}
}
//--------------------
// Accessibility
//--------------------
override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
super.onInitializeAccessibilityEvent(event)
event.className = SurfaceRenderView::class.java.name
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(info)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
info.className = SurfaceRenderView::class.java.name
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/render/TextureRenderView.kt
================================================
package com.jennifer.andy.simpleeyes.player.render
import android.annotation.TargetApi
import android.content.Context
import android.graphics.SurfaceTexture
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.TextureView
import android.view.View
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.jennifer.andy.simpleeyes.player.MeasureHelper
import com.jennifer.andy.simpleeyes.player.render.IRenderView.IRenderCallback
import com.jennifer.andy.simpleeyes.player.render.IRenderView.ISurfaceHolder
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.ISurfaceTextureHolder
import tv.danmaku.ijk.media.player.ISurfaceTextureHost
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap
/**
* Author: andy.xwt
* Date: 2020/3/9 22:56
* Description:
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
class TextureRenderView : TextureView, IRenderView {
private lateinit var mMeasureHelper: MeasureHelper
constructor(context: Context) : super(context) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initView(context)
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
initView(context)
}
private fun initView(context: Context) {
mMeasureHelper = MeasureHelper(this)
mSurfaceCallback = SurfaceCallback(this)
surfaceTextureListener = mSurfaceCallback
}
override fun getView(): View {
return this
}
override fun shouldWaitForResize(): Boolean {
return false
}
override fun onDetachedFromWindow() {
mSurfaceCallback.willDetachFromWindow()
super.onDetachedFromWindow()
mSurfaceCallback.didDetachFromWindow()
}
//--------------------
// Layout & Measure
//--------------------
override fun setVideoSize(videoWidth: Int, videoHeight: Int) {
if (videoWidth > 0 && videoHeight > 0) {
mMeasureHelper.setVideoSize(videoWidth, videoHeight)
requestLayout()
}
}
override fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int) {
if (videoSarNum > 0 && videoSarDen > 0) {
mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen)
requestLayout()
}
}
override fun setVideoRotation(degree: Int) {
mMeasureHelper.setVideoRotation(degree)
rotation = degree.toFloat()
}
override fun setAspectRatio(aspectRatio: Int) {
mMeasureHelper.setAspectRatio(aspectRatio)
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(mMeasureHelper.measuredWidth, mMeasureHelper.measuredHeight)
}
//--------------------
// TextureViewHolder
//--------------------
fun getSurfaceHolder(): ISurfaceHolder {
return InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback)
}
/**
* 内部surfaceHolder
*/
private class InternalSurfaceHolder(private val textureView: TextureRenderView,
override val surfaceTexture: SurfaceTexture?,
private val mSurfaceTextureHost: ISurfaceTextureHost) : ISurfaceHolder {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
override fun bindToMediaPlayer(mp: IMediaPlayer?) {
if (mp == null) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
mp is ISurfaceTextureHolder) {
val textureHolder = mp as ISurfaceTextureHolder
textureView.mSurfaceCallback.setOwnSurfaceTexture(false)
val surfaceTexture = textureHolder.surfaceTexture
if (surfaceTexture != null) {
textureView.surfaceTexture = surfaceTexture
} else {
textureHolder.surfaceTexture = this.surfaceTexture
textureHolder.setSurfaceTextureHost(textureView.mSurfaceCallback)
}
} else {
mp.setSurface(openSurface())
}
}
override val renderView: IRenderView
get() = textureView
override val surfaceHolder: SurfaceHolder?
get() = null
override fun openSurface(): Surface? {
return if (surfaceTexture == null) null else Surface(surfaceTexture)
}
}
//-------------------------
// SurfaceHolder.Callback
//-------------------------
override fun addRenderCallback(callback: IRenderCallback) {
mSurfaceCallback.addRenderCallback(callback)
}
override fun removeRenderCallback(callback: IRenderCallback) {
mSurfaceCallback.removeRenderCallback(callback)
}
private lateinit var mSurfaceCallback: SurfaceCallback
/**
* SurfaceTexture监听
*/
private class SurfaceCallback(renderView: TextureRenderView) : SurfaceTextureListener, ISurfaceTextureHost {
var mSurfaceTexture: SurfaceTexture? = null
private var mIsFormatChanged = false
private var mWidth = 0
private var mHeight = 0
private var mOwnSurfaceTexture = true
private var mWillDetachFromWindow = false
private var mDidDetachFromWindow = false
private val mWeakRenderView: WeakReference = WeakReference(renderView)
private val mRenderCallbackMap: MutableMap = ConcurrentHashMap()
fun setOwnSurfaceTexture(ownSurfaceTexture: Boolean) {
mOwnSurfaceTexture = ownSurfaceTexture
}
/**
* 添加渲染回调
*/
fun addRenderCallback(callback: IRenderCallback) {
mRenderCallbackMap[callback] = callback
var surfaceHolder: ISurfaceHolder? = null
if (mSurfaceTexture != null) {
if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, mSurfaceTexture, this)
callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight)
}
if (mIsFormatChanged) {
if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, mSurfaceTexture, this)
callback.onSurfaceChanged(surfaceHolder, 0, mWidth, mHeight)
}
}
fun removeRenderCallback(callback: IRenderCallback) {
mRenderCallbackMap.remove(callback)
}
/**
* 当纹理面板可用的时候
*/
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
mSurfaceTexture = surface
mIsFormatChanged = false
mWidth = 0
mHeight = 0
val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, surface, this)
for (renderCallback in mRenderCallbackMap.keys) {
renderCallback.onSurfaceCreated(surfaceHolder, 0, 0)
}
}
/**
* 当纹理面板大小发生改变的时候
*/
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
mSurfaceTexture = surface
mIsFormatChanged = true
mWidth = width
mHeight = height
val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, surface, this)
for (renderCallback in mRenderCallbackMap.keys) {
renderCallback.onSurfaceChanged(surfaceHolder, 0, width, height)
}
}
/**
* 当表面纹理摧毁的时候
*/
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
mSurfaceTexture = surface
mIsFormatChanged = false
mWidth = 0
mHeight = 0
val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, surface, this)
for (renderCallback in mRenderCallbackMap.keys) {
renderCallback.onSurfaceDestroyed(surfaceHolder)
}
Log.d(TAG, "onSurfaceTextureDestroyed: destroy: $mOwnSurfaceTexture")
return mOwnSurfaceTexture
}
/**
* 当表面纹理更新的时候
*/
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
//-------------------------
// ISurfaceTextureHost
//-------------------------
override fun releaseSurfaceTexture(surfaceTexture: SurfaceTexture) {
if (surfaceTexture == null) {
Log.d(TAG, "releaseSurfaceTexture: null")
} else if (mDidDetachFromWindow) {
if (surfaceTexture !== mSurfaceTexture) {
Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release different SurfaceTexture")
surfaceTexture.release()
} else if (!mOwnSurfaceTexture) {
Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release detached SurfaceTexture")
surfaceTexture.release()
} else {
Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): already released by TextureView")
}
} else if (mWillDetachFromWindow) {
if (surfaceTexture !== mSurfaceTexture) {
Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): release different SurfaceTexture")
surfaceTexture.release()
} else if (!mOwnSurfaceTexture) {
Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): re-attach SurfaceTexture to TextureView")
setOwnSurfaceTexture(true)
} else {
Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): will released by TextureView")
}
} else {
if (surfaceTexture !== mSurfaceTexture) {
Log.d(TAG, "releaseSurfaceTexture: alive: release different SurfaceTexture")
surfaceTexture.release()
} else if (!mOwnSurfaceTexture) {
Log.d(TAG, "releaseSurfaceTexture: alive: re-attach SurfaceTexture to TextureView")
setOwnSurfaceTexture(true)
} else {
Log.d(TAG, "releaseSurfaceTexture: alive: will released by TextureView")
}
}
}
/**
* 将要从window消失
*/
fun willDetachFromWindow() {
Log.d(TAG, "willDetachFromWindow()")
mWillDetachFromWindow = true
}
/**
* 已经从window消失
*/
fun didDetachFromWindow() {
Log.d(TAG, "didDetachFromWindow()")
mDidDetachFromWindow = true
}
}
//--------------------
// Accessibility
//--------------------
override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
super.onInitializeAccessibilityEvent(event)
event.className = TextureRenderView::class.java.name
}
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(info)
info.className = TextureRenderView::class.java.name
}
companion object {
private const val TAG = "TextureRenderView"
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ControllerView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.MediaController.MediaPlayerControl
import com.jennifer.andy.simpleeyes.entity.ContentBean
import com.jennifer.andy.simpleeyes.player.IjkMediaController
import com.jennifer.andy.simpleeyes.player.event.VideoProgressEvent
import com.jennifer.andy.simpleeyes.utils.stringForTime
import java.util.*
/**
* Author: andy.xwt
* Date: 2020/3/9 23:00
* Description:
*/
abstract class ControllerView(context: Context) : FrameLayout(context) {
protected lateinit var mPlayer: MediaPlayerControl
protected lateinit var mController: IjkMediaController
protected var mCurrentVideoInfo: ContentBean? = null
var isDragging = false//是否正在拖动
private var mErrorView: ErrorView? = null
private val mContentViews: MutableList = ArrayList()
abstract val layoutId: Int
fun initControllerAndData(player: MediaPlayerControl, controller: IjkMediaController, currentVideoInfo: ContentBean?) {
mPlayer = player
mController = controller
mCurrentVideoInfo = currentVideoInfo
LayoutInflater.from(context).inflate(layoutId, this, true)
initView()
initData()
}
/**
* 初始化布局
*/
abstract fun initView()
/**
* 初始化数据
*/
abstract fun initData()
/**
* 显示
*/
fun display() {
updateTogglePauseUI(mPlayer.isPlaying)
}
/**
* 更新暂停切换UI
*
* @param isPlaying 是否播放
*/
open fun updateTogglePauseUI(isPlaying: Boolean) {}
/**
* 更新当前视频播放进度
*
* @param progress 第一进度
* @param secondaryProgress 第二进度
*/
open fun updateProgress(progress: Int, secondaryProgress: Int) {}
/**
* 更新当前视频播放时间
*
* @param currentTime 当前时间
* @param endTime 结束时间
*/
open fun updateTime(currentTime: String, endTime: String) {}
/**
* 根据传入的[videoProgressEvent] 更新播放进度与时间
*
* @param videoProgressEvent 播放进度事件
*/
fun updateProgressAndTime(videoProgressEvent: VideoProgressEvent) {
if (!isDragging) { //没在拖动的时候跟新进度条
updateProgress(videoProgressEvent.progress, videoProgressEvent.secondaryProgress)
updateTime(stringForTime(videoProgressEvent.currentPosition), stringForTime(videoProgressEvent.duration))
}
}
/**
* 暂停切换
*/
fun togglePause() {
if (mPlayer.isPlaying) {
mPlayer.pause()
} else {
mPlayer.start()
}
updateTogglePauseUI(mPlayer.isPlaying)
}
internal enum class State {
ERROR
}
override fun addView(child: View?, params: ViewGroup.LayoutParams?) {
super.addView(child, params)
if (child != null && child.tag !== State.ERROR) mContentViews.add(child)
}
/**
* 显示错误界面
*/
fun showErrorView() {
for (contentView in mContentViews) {
contentView.visibility = View.GONE
}
if (mErrorView == null) {
mErrorView = ErrorView(context).apply {
setController(mController)
tag = State.ERROR
}
addView(mErrorView)
} else {
mErrorView!!.visibility = View.VISIBLE
}
}
/**
* 显示内容界面,默认会将错误界面隐藏
*/
fun showContent() {
for (contentView in mContentViews) {
contentView.visibility = View.VISIBLE
}
if (mErrorView != null) {
mErrorView!!.visibility = View.GONE
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ControllerViewFactory.kt
================================================
package com.jennifer.andy.simpleeyes.player.view
import android.content.Context
/**
* Author: andy.xwt
* Date: 2020/3/9 23:26
* Description:
*/
class ControllerViewFactory {
companion object {
const val TINY_MODE = 0
const val FULL_SCREEN_MODE = 1
}
fun create(showMode: Int, context: Context): ControllerView {
return when (showMode) {
TINY_MODE -> TinyControllerView(context)
FULL_SCREEN_MODE -> FullScreenControllerView(context)
else -> throw IllegalArgumentException("not correct showMode")
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ErrorView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.player.IjkMediaController
/**
* Author: andy.xwt
* Date: 2020/3/9 9:19 PM
* Description: 错误界面,用于[ControllerView]显示的网络错误界面。
*/
class ErrorView(context: Context) : FrameLayout(context), View.OnClickListener {
private val mIvReload: ImageView
private lateinit var mController: IjkMediaController
fun setController(controller: IjkMediaController) {
mController = controller
}
override fun onClick(v: View) {
val rotation = ObjectAnimator.ofFloat(mIvReload, "rotation", 0f, 360f)
rotation.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { //执行选中动画,然后开始刷新操作
mController.hide()
mController.controllerListener?.onErrorViewClick()
}
})
rotation.duration = 500
rotation.start()
}
init {
LayoutInflater.from(context).inflate(R.layout.layout_video_error, this, true)
mIvReload = findViewById(R.id.iv_reload)
setOnClickListener(this)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/FullScreenControllerView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view
import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.SeekBar
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.utils.stringForTime
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
/**
* Author: andy.xwt
* Date: 2020/3/9 23:17
* Description:
*/
class FullScreenControllerView(context: Context) : ControllerView(context), View.OnClickListener {
private lateinit var mMinScreen: ImageView
private lateinit var mPreButton: ImageView
private lateinit var mPauseButton: ImageView
private lateinit var mNextButton: ImageView
private lateinit var mProgress: SeekBar
private lateinit var mTitle: CustomFontTextView
private lateinit var mEndTime: CustomFontTextView
private lateinit var mCurrentTime: CustomFontTextView
private var mChangeProgress = 0
companion object {
const val FULL_SCREEN_ID = 100
}
override val layoutId = R.layout.layout_media_controller_full_screen
override fun initView() {
mMinScreen = findViewById(R.id.iv_min_screen)
mProgress = findViewById(R.id.sb_progress)
mTitle = findViewById(R.id.tv_title)
mPreButton = findViewById(R.id.iv_previous)
mPauseButton = findViewById(R.id.iv_pause)
mNextButton = findViewById(R.id.iv_next)
mProgress = findViewById(R.id.sb_progress)
mCurrentTime = findViewById(R.id.tv_currentTime)
mEndTime = findViewById(R.id.tv_end_time)
initListener()
}
private fun initListener() {
mPreButton.setOnClickListener(this)
mMinScreen.setOnClickListener(this)
mPauseButton.setOnClickListener(this)
mNextButton.setOnClickListener(this)
setOnClickListener(this)
mProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onStartTrackingTouch(bar: SeekBar) { //控制的时候停止更新进度条,同时禁止隐藏
isDragging = true
mController.showAllTheTime()
}
override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) { //更新当前播放时间
val duration = mPlayer.duration.toLong()
val newPosition = duration * progress / 1000L
mChangeProgress = progress
mCurrentTime.text = stringForTime(newPosition.toInt())
}
}
override fun onStopTrackingTouch(bar: SeekBar) { //定位都拖动位置
val newPosition = mPlayer.duration * mChangeProgress / 1000L
mPlayer.seekTo(newPosition.toInt())
mController.show() //开启延时隐藏
isDragging = false
}
})
}
override fun initData() {
//初始化标题
mTitle.text = mCurrentVideoInfo!!.title
//初始化播放时间
val position = mPlayer.currentPosition
val duration = mPlayer.duration
mCurrentTime.text = stringForTime(position)
mEndTime.text = stringForTime(duration)
//初始化进度条
mProgress.apply {
max = 1000
if (duration >= 0 && mPlayer.bufferPercentage > 0) {
val firstProgress = 1000L * position / duration
val secondProgress = mPlayer.bufferPercentage * 10
progress = firstProgress.toInt()
secondaryProgress = secondProgress
}
setPadding(0, 0, 0, 0)
}
updatePreNextButton()
id = FULL_SCREEN_ID
}
//判断是否显示上一个按钮与下一个按钮
private fun updatePreNextButton() {
mPreButton.visibility = if (mController.isHavePreVideo()) View.VISIBLE else View.GONE
mNextButton.visibility = if (mController.isHaveNextVideo()) View.VISIBLE else View.GONE
}
override fun onClick(v: View) {
when (v.id) {
FULL_SCREEN_ID -> mController.hide()
R.id.iv_pause -> {
togglePause()
mController.show()
}
R.id.iv_previous -> {
mController.controllerListener?.onPreClick()
updatePreNextButton()
}
R.id.iv_next -> {
mController.controllerListener?.onNextClick()
updatePreNextButton()
}
R.id.iv_min_screen -> {
mController.switchControllerView(ControllerViewFactory.TINY_MODE)
mController.controllerListener?.onTinyScreenClick()
}
}
}
override fun updateProgress(progress: Int, secondaryProgress: Int) {
mProgress.progress = progress
mProgress.secondaryProgress = secondaryProgress
}
override fun updateTime(currentTime: String, endTime: String) {
mCurrentTime.text = currentTime
mEndTime.text = endTime
}
override fun updateTogglePauseUI(isPlaying: Boolean) {
if (isPlaying) {
mPauseButton.setImageResource(R.drawable.ic_player_pause)
} else {
mPauseButton.setImageResource(R.drawable.ic_player_play)
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/TinyControllerView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view
import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.utils.stringForTime
/**
* Author: andy.xwt
* Date: 2020/3/9 23:37
* Description:
*/
class TinyControllerView(context: Context) : ControllerView(context), View.OnClickListener {
private lateinit var mPauseButton: ImageView
private lateinit var mPreButton: ImageView
private lateinit var mBackButton: ImageView
private lateinit var mNextButton: ImageView
private lateinit var mFullScreen: ImageView
private lateinit var mEndTime: TextView
private lateinit var mCurrentTime: TextView
override val layoutId = R.layout.layout_media_controller_tiny
override fun initView() {
mPauseButton = findViewById(R.id.iv_pause)
mPreButton = findViewById(R.id.iv_previous)
mBackButton = findViewById(R.id.iv_back)
mNextButton = findViewById(R.id.iv_next)
mFullScreen = findViewById(R.id.iv_full_screen)
mEndTime = findViewById(R.id.tv_end_time)
mCurrentTime = findViewById(R.id.tv_currentTime)
initListener()
}
private fun initListener() {
mPreButton.setOnClickListener(this)
mPauseButton.setOnClickListener(this)
mNextButton.setOnClickListener(this)
mBackButton.setOnClickListener(this)
mFullScreen.setOnClickListener(this)
}
override fun initData() { //初始化开始时间
val position = mPlayer.currentPosition
val duration = mPlayer.duration
mCurrentTime.text = stringForTime(position)
mEndTime.text = "/${stringForTime(duration)}"
updatePreNextButton()
}
//判断是否显示上一个按钮与下一个按钮
private fun updatePreNextButton() {
mPreButton.visibility = if (mController.isHavePreVideo()) View.VISIBLE else View.GONE
mNextButton.visibility = if (mController.isHaveNextVideo()) View.VISIBLE else View.GONE
}
override fun onClick(v: View) {
when (v.id) {
R.id.iv_pause -> {
togglePause()
mController.show()
}
R.id.iv_previous -> {
mController.controllerListener?.onPreClick()
updatePreNextButton()
}
R.id.iv_next -> {
mController.controllerListener?.onNextClick()
updatePreNextButton()
}
R.id.iv_back -> {
mController.hide()
mController.controllerListener?.onBackClick()
}
R.id.iv_full_screen -> {
mController.switchControllerView(ControllerViewFactory.FULL_SCREEN_MODE)
mController.controllerListener?.onFullScreenClick()
}
}
}
override fun updateProgress(progress: Int, secondaryProgress: Int) {}
override fun updateTime(currentTime: String, endTime: String) {
mCurrentTime.text = currentTime
mEndTime.text = "/$endTime"
}
override fun updateTogglePauseUI(isPlaying: Boolean) {
if (isPlaying) {
mPauseButton.setImageResource(R.drawable.ic_player_pause)
} else {
mPauseButton.setImageResource(R.drawable.ic_player_play)
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/router/EyesPathReplaceService.kt
================================================
package com.jennifer.andy.simpleeyes.router
import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.facade.service.PathReplaceService
import java.util.regex.Pattern
/**
* Author: andy.xwt
* Date: 2018/7/20 14:08
* Description:
* 这里发现开眼的ActionUrl不是正常的scheme格式,因为当我们通过Uri跳转的时候会出现问题。所以这里要对其进行替换操作
*/
@Route(path = "/github/AndyJennifer")
class EyesPathReplaceService : PathReplaceService {
companion object {
const val HOST = "eyepetizer://github.com/"
const val SCHEME = "eyepetizer"
const val ONE_LEVEL = "AndyJennifer/"
}
override fun forString(path: String): String {
return path
}
override fun forUri(uri: Uri): Uri {
if (uri.scheme == SCHEME) {
val uriStr = uri.toString()
val split = uriStr.split("eyepetizer://")
when {
uriStr.contains("pgc/detail") -> //处理作者详情
return setPageDetailUrl(split)
uriStr.contains("webview") -> //处理webview
return setWebViewUrl(split)
uriStr.contains("detail") -> //处理webview中点击跳转
return setWebVideoUrl(split)
uriStr.contains("ranklist") ->//处理排行榜
return setRankListUrl(split)
uriStr.contains("campaign") ->//处理专题
return setTopicUrl(split)
uriStr.contains("tag") ->//处理360全景
return setTagUrl(split)
uriStr.contains("category") ->//处理分类
return setCategoryUrl(split)
uriStr.contains("common") ->//处理公共RecyclerView界面
return setCommonUrl(split)
else -> {
}
}
}
return uri
}
/**
* 处理作者详情url
* eyepetizer://pgc/detail/1065/?title=DELISH%20KITCHEN%20%E5%8F%AF%E5%8F%A3%E5%8E%A8%E6%88%BF&userType=PGC&tabIndex=1
* 替换为:
* eyepetizer://github.com/pgc/detail/?title=DELISH%20KITCHEN%20%E5%8F%AF%E5%8F%A3%E5%8E%A8%E6%88%BF&userType=PGC&tabIndex=1?id=1065
*/
private fun setPageDetailUrl(split: List): Uri {
//获取id
val pt = Pattern.compile("/\\d+/")
val matcher = pt.matcher(split[1])
var id = 0
if (matcher.find()) {
id = matcher.group().replace("/", "").toInt()
}
//替换/1065/为空字符串
val uriWithoutNumber = split[1].replace(Regex("/\\d+/"), "")
return "$HOST$uriWithoutNumber&id=$id".toUri()
}
/**
* 处理webViewUrl
* eyepetizer://webview/?title=%E5%B9%BF%E5%9C%BA&url=http%3A%2F%2Fwww.kaiyanapp.com%2Fcampaign%2Ftag_square%2Ftag_square.html%3Fnid%3D1207%26tid%3D845%26cookie%3D%26udid%3Dd0f6190461864a3a978bdbcb3fe9b48709f1f390%26shareable%3Dtrue
* 替换为:eyepetizer://github.com/AndyJennifer/webview/?title=%E5%B9%BF%E5%9C%BA&url=http%3A%2F%2Fwww.kaiyanapp.com%2Fcampaign%2Ftag_square%2Ftag_square.html%3Fnid%3D1207%26tid%3D845%26cookie%3D%26udid%3Dd0f6190461864a3a978bdbcb3fe9b48709f1f390%26shareable%3Dtrue
*/
private fun setWebViewUrl(split: List): Uri {
return Uri.parse("$HOST$ONE_LEVEL${split[1]}")
}
/**
* 处理webView点击跳转视频信息
* eyepetizer://detail/114760
* 替换为:eyepetizer://github.com/AndyJennifer/detail?id =114760
*/
private fun setWebVideoUrl(split: List): Uri {
//获取id
val pt = Pattern.compile("/\\d+")
val matcher = pt.matcher(split[1])
var id = 0
if (matcher.find()) {
id = matcher.group().replace("/", "").toInt()
}
//替换/1065/为空字符串
val uriWithoutNumber = split[1].replace(Regex("/\\d+"), "")
return "$HOST$ONE_LEVEL$uriWithoutNumber?id=$id".toUri()
}
/**
* 处理排行榜
* eyepetizer://ranklist/
* 替换为:eyepetizer://github.com/AndyJennifer/ranklist
*/
private fun setRankListUrl(split: List): Uri {
val str = split[1].replace("/", "")
return "$HOST$ONE_LEVEL$str".toUri()
}
/**
* 处理热门专题,
* eyepetizer://campaign/list/?title=%E4%B8%93%E9%A2%98
* 替换为:eyepetizer://github.com/campaign/list?title=%E4%B8%93%E9%A2%98
*/
private fun setTopicUrl(split: List): Uri {
val str = split[1].replace("/?", "?")
return "$HOST$str".toUri()
}
/**
* 处理360全景
* eyepetizer://tag/658/?title=360%C2%B0%E5%85%A8%E6%99%AF
* 替换为:eyepetizer://github.com/AndyJennifer/tag?id=658&title=360%C2%B0%E5%85%A8%E6%99%AF
*/
private fun setTagUrl(split: List): Uri {
//获取id
val pt = Pattern.compile("/\\d+/")
val matcher = pt.matcher(split[1])
var id = 0
if (matcher.find()) {
id = matcher.group().replace("/", "").toInt()
}
//替换/658/为空字符串
val uriWithoutNumber = split[1].replace(Regex("/\\d+/"), "")
return "$HOST$ONE_LEVEL$uriWithoutNumber&id=$id".toUri()
}
/**
* 处理360全景下面的所有的种类
* eyepetizer://category/14/?title=%E5%B9%BF%E5%91%8A
* 替换为:eyepetizer://github.com/AndyJennifer/category?id=14%title=%E5%B9%BF%E5%91%8A
*/
private fun setCategoryUrl(split: List): Uri {
val pt = Pattern.compile("/\\d+/")
val matcher = pt.matcher(split[1])
var id = 0
if (matcher.find()) {
id = matcher.group().replace("/", "").toInt()
}
val uriWithoutNumber = split[1].replace(Regex("/\\d+/"), "")
return "$HOST$ONE_LEVEL$uriWithoutNumber&id=$id".toUri()
}
/**
* 处理公共界面中包含RecyclerVie的
* eyepetizer://common/?title=VLOG&url=http%3A%2F%2Fbaobab.kaiyanapp.com%2Fapi%2Fv4%2Fplaylists%2F3946%2Fvideos
* 替换为:eyepetizer://github.com/AndyJennifer/common?title=VLOG&url=http%3A%2F%2Fbaobab.kaiyanapp.com%2Fapi%2Fv4%2Fplaylists%2F3946%2Fvideos
*/
private fun setCommonUrl(split: List): Uri {
val str = split[1].replace("/?", "?")
return "$HOST$ONE_LEVEL$str".toUri()
}
override fun init(context: Context?) {
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/router/RouteIntercept.kt
================================================
package com.jennifer.andy.simpleeyes.router
import android.content.Context
import com.alibaba.android.arouter.facade.Postcard
import com.alibaba.android.arouter.facade.annotation.Interceptor
import com.alibaba.android.arouter.facade.callback.InterceptorCallback
import com.alibaba.android.arouter.facade.template.IInterceptor
import com.alibaba.android.arouter.launcher.ARouter
import com.jennifer.andy.simpleeyes.UserPreferences
/**
* Author: andy.xwt
* Date: 2019/1/14 19:04
* Description: 1.登录拦截,判断用户是否登录
*/
@Interceptor(priority = 8, name = "登录拦截器")
class RouteIntercept : IInterceptor {
companion object {
const val SHOULD_LOGIN = 1 ushr 30
}
override fun process(postcard: Postcard, callback: InterceptorCallback) {
when (postcard.extra) {
SHOULD_LOGIN -> judgeUserIsLogin(postcard, callback)
else -> {
callback.onContinue(postcard)
}
}
}
/**
* 判断用户是否登录,如果没有登录则显示登录界面
*/
private fun judgeUserIsLogin(postcard: Postcard, callback: InterceptorCallback) {
val userIsLogin = UserPreferences.getUserIsLogin()
if (!userIsLogin) //如果用户没登录,直接跳转到登录界面
ARouter.getInstance().build("/github/Login").navigation()
else
callback.onContinue(postcard)
}
override fun init(context: Context?) {
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/router/SchemeFilterActivity.kt
================================================
package com.jennifer.andy.simpleeyes.router
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.android.arouter.facade.Postcard
import com.alibaba.android.arouter.facade.callback.NavCallback
import com.alibaba.android.arouter.launcher.ARouter
/**
* Author: andy.xwt
* Date: 2018/7/19 19:57
* Description: 中间跳转页
*/
class SchemeFilterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri = intent.data
ARouter.getInstance().build(uri).navigation(this, object : NavCallback() {
override fun onArrival(postcard: Postcard?) {
finish()
}
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/RxBus.kt
================================================
package com.jennifer.andy.simpleeyes.rx
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import java.util.concurrent.ConcurrentHashMap
/**
* Author: andy.xwt
* Date: 2018/2/12 14:06
* Description: 参考地址:https://mcxiaoke.gitbooks.io/rxdocs/content/Subject.html
*/
object RxBus {
private val mBus = PublishSubject.create().toSerialized()
private val mSubjects = ConcurrentHashMap()
/**
* 发送一个事件
*/
@JvmStatic
fun post(o: Any) {
mBus.onNext(o)
}
/**
* 根据传递的 eventType 类型返回特定类型的(eventType)的 被观察者
*/
private fun toObservable(eventType: Class): Observable {
return mBus.ofType(eventType)
}
/**
* 订阅
*
* @param eventType 接受的事件类型
* @param action 处理方法
* @param error 错误处理方法
* @param 接受事件的泛型
* @return
*/
private fun doSubscribe(eventType: Class, action: Consumer, error: Consumer): Disposable {
return toObservable(eventType)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(action, error)
}
/**
* 保存订阅后的disposable
*/
private fun addDisposable(any: Any, disposable: Disposable) {
val key = any.javaClass.name
if (mSubjects[key] != null) {
mSubjects[key]?.add(disposable)
} else {
val disposables = CompositeDisposable()
disposables.add(disposable)
mSubjects[key] = disposables
}
}
/**
* 处理注册事件
*
* @param event 事件
* @param action 处理方式
* @param subscribe 订阅者
* @param 事件类型泛型
*/
@JvmStatic
fun register(subscribe: Any, event: Class, action: Consumer) {
val disposable = doSubscribe(event, action, Consumer { throwable -> throw RuntimeException(throwable.message) })
addDisposable(subscribe, disposable)
}
/**
* 注销监听事件
*
* @param subscribe 订阅者
*/
@JvmStatic
fun unRegister(subscribe: Any) {
val key = subscribe.javaClass.name
if (mSubjects.containsKey(key) && mSubjects[key] != null) {
mSubjects[key]?.dispose()
}
mSubjects.remove(key)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/RxThreadHelper.kt
================================================
package com.jennifer.andy.simpleeyes.rx
import io.reactivex.FlowableTransformer
import io.reactivex.MaybeTransformer
import io.reactivex.ObservableTransformer
import io.reactivex.SingleTransformer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/**
* Author: andy.xwt
* Date: 2017/10/24 10:42
* Description:网络请求帮助类处理。默认将观察者的方法切换到主线程中运行
*/
object RxThreadHelper {
/**
* 将Observable类型的观察者切换到主线程中运行
*/
fun switchObservableThread(): ObservableTransformer {
return ObservableTransformer {
it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
}
}
/**
* 将Flowable类型的观察者切换到主线程中运行
*/
fun switchFlowableThread(): FlowableTransformer {
return FlowableTransformer {
it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
}
}
/**
* 将Single类型的观察者切换到主线程中运行
*/
fun switchSingleThread(): SingleTransformer {
return SingleTransformer {
it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
}
}
/**
* 将Maybe类型的观察者切换到主线程中运行
*/
fun switchMaybeThread(): MaybeTransformer {
return MaybeTransformer {
it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/FlowableRetryDelay.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error
import io.reactivex.Flowable
import io.reactivex.functions.Function
import org.reactivestreams.Publisher
import java.util.concurrent.TimeUnit
/**
* Author: andy.xwt
* Date: 2019-11-17 21:24
* Description:该类逻辑与[ObservableRetryDelay]一样
*/
class FlowableRetryDelay(
val retryConfigProvider: (Throwable) -> RetryConfig
) : Function, Publisher<*>> {
private var retryCount: Int = 0
override fun apply(throwableFlowable: Flowable): Publisher<*> {
return throwableFlowable
.flatMap { error ->
val (maxRetries, delay, retryTransform) = retryConfigProvider(error)
if (++retryCount <= maxRetries) {
retryTransform()
.flatMapPublisher { retry ->
if (retry)
Flowable.timer(delay.toLong(), TimeUnit.MILLISECONDS)
else
Flowable.error(error)
}
} else Flowable.error(error)
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/GlobalErrorProcessor.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error
import com.jennifer.andy.simpleeyes.utils.toast
import retrofit2.HttpException
/**
* Author: andy.xwt
* Date: 2019-11-17 22:04
* Description:
*/
fun globalHandleError(): GlobalErrorTransformer = GlobalErrorTransformer(
globalDoOnErrorConsumer = { error ->
when (error) {
is HttpException -> {
when (error.code()) {
401 -> toast { "401 Unauthorized" }
404 -> toast { "404 failure" }
500 -> toast { "500 server failure" }
else -> toast { "network failure" }
}
}
else -> {
toast { error.message.toString() }
}
}
}
)
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/GlobalErrorTransformer.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error
import io.reactivex.*
/**
* Author: andy.xwt
* Date: 2019-11-17 21:22
* Description: RxJava全局错误处理,包括:
* 1.全局的onNext拦截器[globalOnNextInterceptor]
* 2.全局的错误重试处理器[globalOnErrorResume],该属性用于当发生错误时,重新发送新的Observable
* 3.全局重试配置参数[RetryConfig],该属性用于配置重试次数以及重试的时间,是否需要重试,需要通过内部的condition进行判断
* 4.全局的错误处理器[globalDoOnErrorConsumer],该属性会在观察者的onError方法之前运行
*/
class GlobalErrorTransformer constructor(
private val globalOnNextInterceptor: (T) -> Observable = { Observable.just(it) },
private val globalOnErrorResume: (Throwable) -> Observable = { Observable.error(it) },
private val retryConfigProvider: (Throwable) -> RetryConfig = { RetryConfig() },
private val globalDoOnErrorConsumer: (Throwable) -> Unit = { }
) : ObservableTransformer,
FlowableTransformer,
SingleTransformer,
MaybeTransformer,
CompletableTransformer {
override fun apply(upstream: Observable): Observable =
upstream
.flatMap {
globalOnNextInterceptor(it)
}
.onErrorResumeNext { throwable: Throwable ->
globalOnErrorResume(throwable)
}
.retryWhen(ObservableRetryDelay(retryConfigProvider))
.doOnError(globalDoOnErrorConsumer)
override fun apply(upstream: Completable): Completable =
upstream
.onErrorResumeNext {
globalOnErrorResume(it).ignoreElements()
}
.retryWhen(FlowableRetryDelay(retryConfigProvider))
.doOnError(globalDoOnErrorConsumer)
override fun apply(upstream: Flowable): Flowable =
upstream
.flatMap {
globalOnNextInterceptor(it)
.toFlowable(BackpressureStrategy.BUFFER)
}
.onErrorResumeNext { throwable: Throwable ->
globalOnErrorResume(throwable)
.toFlowable(BackpressureStrategy.BUFFER)
}
.retryWhen(FlowableRetryDelay(retryConfigProvider))
.doOnError(globalDoOnErrorConsumer)
override fun apply(upstream: Maybe): Maybe =
upstream
.flatMap {
globalOnNextInterceptor(it)
.firstElement()
}
.onErrorResumeNext { throwable: Throwable ->
globalOnErrorResume(throwable)
.firstElement()
}
.retryWhen(FlowableRetryDelay(retryConfigProvider))
.doOnError(globalDoOnErrorConsumer)
override fun apply(upstream: Single): Single =
upstream
.flatMap {
globalOnNextInterceptor(it)
.firstOrError()
}
.onErrorResumeNext { throwable ->
globalOnErrorResume(throwable)
.firstOrError()
}
.retryWhen(FlowableRetryDelay(retryConfigProvider))
.doOnError(globalDoOnErrorConsumer)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/ObservabeRetryDelay.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error
import io.reactivex.Observable
import io.reactivex.ObservableSource
import io.reactivex.functions.Function
import java.util.concurrent.TimeUnit
/**
* Author: andy.xwt
* Date: 2019-11-17 21:27
* Description:该类会获取配置的重试参数。判断当前重试的次数是否大于配置的重试次数,如果比配置的小,那么会判断重试
* 的条件,如果为true,那么就会重试,反之直接发送Error信息
*/
class ObservableRetryDelay(
val retryConfigProvider: (Throwable) -> RetryConfig
) : Function, ObservableSource<*>> {
private var retryCount: Int = 0
override fun apply(throwableObs: Observable): ObservableSource<*> {
return throwableObs
.flatMap { error ->
val (maxRetries, delay, retryCondition) = retryConfigProvider(error)
if (++retryCount <= maxRetries) {
retryCondition().flatMapObservable { retry ->
if (retry)
Observable.timer(delay.toLong(), TimeUnit.MILLISECONDS)
else
Observable.error(error)
}
} else Observable.error(error)
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/RetryConfig.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error
import io.reactivex.Single
/**
* Author: andy.xwt
* Date: 2019-11-17 21:12
* Description: 重试配置,默认情况下不会进行重试,如果需要重试配置,需要重写condition
*/
private const val DEFAULT_RETRY_TIMES = 1 //默认重试次数 1次
private const val DEFAULT_DELAY_DURATION = 1000 //默认推迟时间为1秒
data class RetryConfig(val maxRetries: Int = DEFAULT_RETRY_TIMES,
val delay: Int = DEFAULT_DELAY_DURATION,
val condition: () -> Single = { Single.just(false) })
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/MainActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui
import android.os.Bundle
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.ui.base.BaseAppCompatActivity
import com.jennifer.andy.simpleeyes.ui.feed.FeedFragment
import com.jennifer.andy.simpleeyes.ui.follow.FollowFragment
import com.jennifer.andy.simpleeyes.ui.home.HomeFragment
import com.jennifer.andy.simpleeyes.ui.profile.ProfileFragment
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.BottomBar
import com.jennifer.andy.simpleeyes.widget.BottomItem
import me.yokeyword.fragmentation.SupportFragment
/**
* Author: andy.xwt
* Date: 2017/9/19 18:54
* Description:主界面
*/
class MainActivity : BaseAppCompatActivity() {
private var mFragments = arrayOfNulls(4)
private val mBottomNavigation: BottomBar by bindView(R.id.bottom_navigation_bar)
companion object {
private const val FIRST = 0
private const val SECOND = 1
private const val THIRD = 2
private const val FOURTH = 3
}
override fun initView(savedInstanceState: Bundle?) {
mFragments[FIRST] = HomeFragment.newInstance()
mFragments[SECOND] = FeedFragment.newInstance()
mFragments[THIRD] = FollowFragment.newInstance()
mFragments[FOURTH] = ProfileFragment.newInstance()
loadMultipleRootFragment(R.id.fl_container, FIRST, *mFragments)
initBottomNavigation()
}
private fun initBottomNavigation() {
val home = BottomItem(R.drawable.ic_tab_strip_icon_feed_selected, getString(R.string.home))
home.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_feed)
val discover = BottomItem(R.drawable.ic_tab_strip_icon_category_selected, getString(R.string.discover))
discover.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_category)
val focus = BottomItem(R.drawable.ic_tab_strip_icon_follow_selected, getString(R.string.focus))
focus.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_follow)
val mine = BottomItem(R.drawable.ic_tab_strip_icon_profile_selected, getString(R.string.mine))
mine.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_profile)
with(mBottomNavigation) {
addItem(home)
addItem(discover)
addItem(focus)
addItem(mine)
initialise()
setOnTabSelectedListener(object : BottomBar.TabSelectedListener {
override fun onTabSelected(position: Int, prePosition: Int) {
showHideFragment(mFragments[position])
}
override fun onTabUnselected(position: Int) {
}
override fun onTabReselected(position: Int) {
if (position == FIRST) {
val categoryFragment = mFragments[FIRST] as HomeFragment
categoryFragment.scrollToTop()
}
}
})
}
}
override fun getContentViewLayoutId() = R.layout.activity_main
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/AuthorTagDetailActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author
import android.animation.ArgbEvaluator
import android.graphics.Color
import android.os.Bundle
import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Tab
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.net.Extras
import com.jennifer.andy.simpleeyes.ui.author.presenter.AuthorTagDetailPresenter
import com.jennifer.andy.simpleeyes.ui.author.ui.AuthorTagDetailView
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.BaseFragmentItemAdapter
import com.jennifer.andy.simpleeyes.ui.feed.TagDetailInfoFragment
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.utils.showKeyboard
import com.jennifer.andy.simpleeyes.widget.StickyNavLayout
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.tab.ShortTabLayout
/**
* Author: andy.xwt
* Date: 2019-07-13 22:38
* Description: 作者详细信息界面
*/
@Route(path = "/pgc/detail")
class AuthorTagDetailActivity : BaseActivity(), AuthorTagDetailView {
private val mToolbar: RelativeLayout by bindView(R.id.tool_bar)
private val mTvTitle: CustomFontTextView by bindView(R.id.tv_title)
private val mStickyNavLayout: StickyNavLayout by bindView(R.id.stick_layout)
private val mViewPager: ViewPager by bindView(R.id.id_sticky_nav_layout_viewpager)
private val mTabLayout: ShortTabLayout by bindView(R.id.id_sticky_nav_layout_nav_view)
private val mTvName: CustomFontTextView by bindView(R.id.tv_name)
private val mTvDesc: CustomFontTextView by bindView(R.id.tv_desc)
private val mTvBrief: CustomFontTextView by bindView(R.id.tv_brief)
private val mIvHead: SimpleDraweeView by bindView(R.id.iv_head)
@Autowired
@JvmField
var tabIndex: String? = null
@Autowired
@JvmField
var title: String? = null
@Autowired
@JvmField
var id: String? = null
override fun getBundleExtras(extras: Bundle) {
with(extras) {
tabIndex = getString(Extras.TAB_INDEX)
title = getString(Extras.TITLE)
id = getString(Extras.ID)
}
}
override fun initView(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
initToolBar(title, 0f)
mPresenter.getAuthorTagDetail(id!!)
}
override fun loadInfoSuccess(tab: Tab) {
mViewPager.adapter = BaseFragmentItemAdapter(supportFragmentManager, initFragments(tab.tabInfo), initTitles(tab.tabInfo))
mTabLayout.setupWithViewPager(mViewPager)
mViewPager.currentItem = tabIndex?.toInt() ?: 0
mStickyNavLayout.setScrollChangeListener(object : StickyNavLayout.ScrollChangeListener {
override fun onScroll(moveRatio: Float) {
initToolBar(title, moveRatio)
}
})
//设置topView信息
with(tab.pgcInfo) {
mTvName.text = name
mTvDesc.text = description
mTvBrief.text = brief
mIvHead.setImageURI(icon)
}
}
private fun initFragments(tabInfo: TabInfo): MutableList {
val fragments = mutableListOf()
for (i in tabInfo.tabList.indices) {
fragments.add(TagDetailInfoFragment.newInstance(tabInfo.tabList[i].apiUrl))
}
return fragments
}
private fun initTitles(tabInfo: TabInfo): MutableList {
val titles = mutableListOf()
for (i in tabInfo.tabList.indices) {
titles.add(tabInfo.tabList[i].name)
}
return titles
}
private fun initToolBar(title: String? = null, titleAlpha: Float = 1f) {
findViewById(R.id.iv_back).setOnClickListener {
showKeyboard(false)
finish()
}
//设置背景渐变
val color = ArgbEvaluator().evaluate(titleAlpha, 0x00FFFFFF, Color.WHITE) as Int
mToolbar.setBackgroundColor(color)
mTvTitle.apply {
setFontType(fontType = FontType.BOLD)
text = title
alpha = titleAlpha
}
}
override fun getContentViewLayoutId() = R.layout.activity_author_tag_detail
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/model/AuthorModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author.model
import com.jennifer.andy.simpleeyes.entity.Tab
import com.jennifer.andy.simpleeyes.net.Api
import com.jennifer.andy.simpleeyes.rx.RxThreadHelper
import com.jennifer.andy.simpleeyes.rx.error.globalHandleError
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
import io.reactivex.Observable
/**
* Author: andy.xwt
* Date: 2019-07-13 22:34
* Description:
*/
class AuthorModel : BaseModel {
/**
* 获取作者
*/
fun getAuthorTagDetail(id: String): Observable =
Api.getDefault()
.getAuthorTagDetail(id)
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/presenter/AuthorTagDetailPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.ui.author.model.AuthorModel
import com.jennifer.andy.simpleeyes.ui.author.ui.AuthorTagDetailView
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2019-07-13 22:40
* Description:
*/
class AuthorTagDetailPresenter : BasePresenter() {
private var mAuthorModel: AuthorModel = AuthorModel()
fun getAuthorTagDetail(id: String) {
mAuthorModel.getAuthorTagDetail(id).autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mView?.loadInfoSuccess(it)
}, {
mView?.showNetError(View.OnClickListener { getAuthorTagDetail(id) })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/ui/AuthorTagDetailView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author.ui
import com.jennifer.andy.simpleeyes.entity.Tab
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2019-07-13 22:39
* Description:
*/
interface AuthorTagDetailView : BaseView {
fun loadInfoSuccess(tab: Tab)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base
import android.os.Bundle
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.utils.getGenericInstance
/**
* Author: andy.xwt
* Date: 2017/9/5 19:04
* Description: 基础类activity
*/
@Suppress("UNCHECKED_CAST")
abstract class BaseActivity> : BaseAppCompatActivity(), BaseView {
protected lateinit var mPresenter: T
override fun onCreate(savedInstanceState: Bundle?) {
mPresenter = getGenericInstance(this, 1)
lifecycle.addObserver(mPresenter)
mPresenter.attachView(this as V, this)
super.onCreate(savedInstanceState)
}
override fun showLoading() {
mMultipleStateView.showLoading()
}
override fun showNetError(onClickListener: View.OnClickListener) {
mMultipleStateView.showNetError(onClickListener)
}
override fun showEmpty(onClickListener: View.OnClickListener) {
mMultipleStateView.showEmpty(onClickListener)
}
override fun showContent() {
mMultipleStateView.showContent()
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseAppCompatActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base
import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.StringRes
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.manager.ActivityManager
import com.jennifer.andy.simpleeyes.utils.showKeyboard
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
import me.yokeyword.fragmentation.SupportActivity
/**
* Author: andy.xwt
* Date: 2017/8/10 22:37
* Description:
*/
abstract class BaseAppCompatActivity : SupportActivity() {
/**
* 上下文对象
*/
protected lateinit var mContext: Context
protected lateinit var mMultipleStateView: MultipleStateView
/**
* 跳转到其他Activity启动或者退出的模式
*/
enum class TransitionMode {
LEFT, RIGHT, TOP, BOTTOM, SCALE, FADE
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initData()
initView(savedInstanceState)
}
override fun onStart() {
super.onStart()
ActivityManager.getInstance().addActivity(this)
}
private fun initData() {
overrideTransitionAnimation()
val extras = intent.extras
if (extras != null) {
getBundleExtras(extras)
}
mContext = this
//添加相应的布局
if (getContentViewLayoutId() != 0) {
mMultipleStateView = MultipleStateView(this)
val view = View.inflate(this, getContentViewLayoutId(), mMultipleStateView)
setContentView(view)
} else {
throw IllegalArgumentException("You must return layout id")
}
}
/**
* 设置activity进入动画
*/
private fun overrideTransitionAnimation() {
if (toggleOverridePendingTransition()) {
when (getOverridePendingTransition()) {
TransitionMode.TOP -> overridePendingTransition(R.anim.top_in, R.anim.no_anim)
TransitionMode.BOTTOM -> overridePendingTransition(R.anim.bottom_in, R.anim.no_anim)
TransitionMode.LEFT -> overridePendingTransition(R.anim.left_in, R.anim.no_anim)
TransitionMode.RIGHT -> overridePendingTransition(R.anim.right_in, R.anim.no_anim)
TransitionMode.FADE -> overridePendingTransition(R.anim.fade_in, R.anim.no_anim)
TransitionMode.SCALE -> overridePendingTransition(R.anim.scale_in, R.anim.no_anim)
}
}
}
override fun finish() {
super.finish()
if (toggleOverridePendingTransition()) {
when (getOverridePendingTransition()) {
TransitionMode.TOP -> overridePendingTransition(0, R.anim.top_out)
TransitionMode.BOTTOM -> overridePendingTransition(0, R.anim.bottom_out)
TransitionMode.LEFT -> overridePendingTransition(0, R.anim.left_out)
TransitionMode.RIGHT -> overridePendingTransition(0, R.anim.right_out)
TransitionMode.FADE -> overridePendingTransition(0, R.anim.fade_out)
TransitionMode.SCALE -> overridePendingTransition(0, R.anim.scale_out)
}
}
}
override fun onStop() {
super.onStop()
ActivityManager.getInstance().removeActivity(this)
}
/**
* 初始化工具栏,默认情况下加粗
*/
protected fun initToolBar(toolbar: ViewGroup, title: String? = null, fontType: FontType = FontType.BOLD) {
val ivBack = toolbar.findViewById(R.id.iv_back)
ivBack.setOnClickListener {
showKeyboard(false)
finish()
}
val tvTitle = toolbar.findViewById(R.id.tv_title)
tvTitle.setFontType(fontType)
tvTitle.text = title
}
/**
* 初始化工具栏,默认情况下加粗
*/
protected fun initToolBar(toolbar: ViewGroup, @StringRes id: Int? = null, fontType: FontType = FontType.BOLD) {
val ivBack = toolbar.findViewById(R.id.iv_back)
ivBack.setOnClickListener {
showKeyboard(false)
finish()
}
val tvTitle = toolbar.findViewById(R.id.tv_title)
tvTitle.setFontType(fontType)
tvTitle.setText(id!!)
}
abstract fun initView(savedInstanceState: Bundle?)
/**
* 获取bundle 中的数据
*/
open fun getBundleExtras(extras: Bundle) {}
/**
* 是否有切换动画
*/
protected open fun toggleOverridePendingTransition() = false
/**
* 获得切换动画的模式
*/
protected open fun getOverridePendingTransition(): TransitionMode? = null
/**
* 获取当前布局id
*/
abstract fun getContentViewLayoutId(): Int
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseAppCompatFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
import me.yokeyword.fragmentation.SupportFragment
/**
* Author: andy.xwt
* Date: 2017/9/5 19:06
* Description:
*/
abstract class BaseAppCompatFragment : SupportFragment() {
protected lateinit var LOG_TAG: String
protected lateinit var mMultipleStateView: MultipleStateView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LOG_TAG = this.javaClass.simpleName
if (arguments != null) {
getBundleExtras(arguments!!)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return if (getContentViewLayoutId() != 0) {
mMultipleStateView = MultipleStateView(context!!)
return View.inflate(context, getContentViewLayoutId(), mMultipleStateView)
} else {
super.onCreateView(inflater, container, savedInstanceState)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView(savedInstanceState)
}
/**
* 获取bundle中相应data
*/
open fun getBundleExtras(extras: Bundle) {}
/**
* 获取资源id
*/
abstract fun getContentViewLayoutId(): Int
/**
* 初始化view
*/
open fun initView(savedInstanceState: Bundle?) {
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.utils.getGenericInstance
/**
* Author: andy.xwt
* Date: 2017/9/5 19:06
* Description:
*/
@Suppress("UNCHECKED_CAST")
abstract class BaseFragment> : BaseAppCompatFragment(), BaseView {
protected lateinit var mPresenter: T
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mPresenter = getGenericInstance(this, 1)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mPresenter.attachView(this as V, this)
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun showLoading() {
mMultipleStateView.showLoading()
}
override fun showNetError(onClickListener: View.OnClickListener) {
mMultipleStateView.showNetError(onClickListener)
}
override fun showEmpty(onClickListener: View.OnClickListener) {
mMultipleStateView.showEmpty(onClickListener)
}
override fun showContent() {
mMultipleStateView.showContent()
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseFragmentItemAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
/**
* Author: andy.xwt
* Date: 2018/7/4 13:59
* Description:基础FragmentPagerAdapter适配器
*/
open class BaseFragmentItemAdapter(fragmentManager: FragmentManager,
private val fragments: MutableList,
private val titles: MutableList) : FragmentPagerAdapter(fragmentManager) {
override fun getItem(position: Int) = fragments[position]
override fun getCount() = fragments.size
override fun getPageTitle(position: Int) = titles[position]
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base
import android.view.View
/**
* Author: andy.xwt
* Date: 2017/9/5 19:09
* Description:基础view
*/
interface BaseView {
/**
* 显示正在加载
*/
fun showLoading()
/**
* 显示内容
*/
fun showContent()
/**
* 显示网络异常
*/
fun showNetError(onClickListener: View.OnClickListener)
/**
* 显示空界面
*/
fun showEmpty(onClickListener: View.OnClickListener)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/LoadMoreView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base
/**
* Author: andy.xwt
* Date: 2018/6/30 01:50
* Description:
*/
interface LoadMoreView : BaseView {
/**
* 加载更多信息成功
*/
fun loadMoreSuccess(data: T)
/**
* 没有更多
*/
fun showNoMore()
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/adapter/BaseDataAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base.adapter
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseQuickAdapter.OnItemClickListener
import com.chad.library.adapter.base.BaseViewHolder
import com.chad.library.adapter.base.util.MultiTypeDelegate
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.entity.ContentBean
import com.jennifer.andy.simpleeyes.ui.home.adapter.CollectionCardCoverAdapter
import com.jennifer.andy.simpleeyes.ui.home.adapter.SquareCollectionAdapter
import com.jennifer.andy.simpleeyes.ui.search.adapter.CollectionBriefAdapter
import com.jennifer.andy.simpleeyes.ui.video.VideoDetailActivity
import com.jennifer.andy.simpleeyes.utils.dip2px
import com.jennifer.andy.simpleeyes.utils.getElapseTimeForShow
import com.jennifer.andy.simpleeyes.widget.EliteImageView
import com.jennifer.andy.simpleeyes.widget.ItemHeaderView
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.image.imageloader.FrescoImageLoader
import com.jennifer.andy.simpleeyes.widget.viewpager.MarginWithIndicatorViewPager
import com.youth.banner.Banner
import com.youth.banner.BannerConfig
import java.util.*
/**
* Author: andy.xwt
* Date: 2017/10/27 16:35
* Description: 基础信息适配器
*/
open class BaseDataAdapter(data: MutableList) : BaseQuickAdapter(data) {
/**
* 卡片类型
*/
companion object {
const val BANNER_TYPE = 0
const val FOLLOW_CARD_TYPE = 1
const val HORIZONTAL_SCROLL_CARD_TYPE = 2
const val VIDEO_COLLECTION_WITH_COVER_TYPE = 3
const val SQUARE_CARD_COLLECTION_TYPE = 4
const val VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE = 5
const val VIDEO_COLLECTION_WITH_BRIEF_TYPE = 6
const val TEXT_CARD_TYPE = 7
const val BRIEF_CARD_TYPE = 8
const val BLANK_CARD_TYPE = 9
const val SQUARE_CARD_TYPE = 10
const val RECTANGLE_CARD_TYPE = 11
const val VIDEO_TYPE = 12
const val VIDEO_BANNER_THREE_TYPE = 13
const val VIDEO_SMALL_CARD_TYPE = 14
const val BANNER = "banner"
const val FOLLOW_CARD = "followCard"
const val HORIZONTAL_CARD = "horizontalScrollCard"
const val VIDEO_COLLECTION_WITH_COVER = "videoCollectionWithCover"
const val SQUARE_CARD_COLLECTION = "squareCardCollection"
const val VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD = "videoCollectionOfHorizontalScrollCard"
const val VIDEO_COLLECTION_WITH_BRIEF = "videoCollectionWithBrief"
const val TEXT_CARD = "textCard"
const val BRIEF_CARD = "briefCard"
const val BLANK_CARD = "blankCard"
const val SQUARE_CARD = "squareCard"
const val RECTANGLE_CARD = "rectangleCard"
const val VIDEO = "video"
const val VIDEO_BANNER_THREE = "banner3"
const val VIDEO_SMALL_CARD = "videoSmallCard"
}
init {
multiTypeDelegate = object : MultiTypeDelegate() {
override fun getItemType(andyInfoItem: Content?): Int {
when (andyInfoItem?.type) {
BANNER -> return BANNER_TYPE
FOLLOW_CARD -> return FOLLOW_CARD_TYPE
HORIZONTAL_CARD -> return HORIZONTAL_SCROLL_CARD_TYPE
VIDEO_COLLECTION_WITH_COVER -> return VIDEO_COLLECTION_WITH_COVER_TYPE
SQUARE_CARD_COLLECTION -> return SQUARE_CARD_COLLECTION_TYPE
VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD -> return VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE
VIDEO_COLLECTION_WITH_BRIEF -> return VIDEO_COLLECTION_WITH_BRIEF_TYPE
TEXT_CARD -> return TEXT_CARD_TYPE
BRIEF_CARD -> return BRIEF_CARD_TYPE
BLANK_CARD -> return BLANK_CARD_TYPE
SQUARE_CARD -> return SQUARE_CARD_TYPE
RECTANGLE_CARD -> return RECTANGLE_CARD_TYPE
VIDEO -> return VIDEO_TYPE
VIDEO_BANNER_THREE -> return VIDEO_BANNER_THREE_TYPE
VIDEO_SMALL_CARD -> return VIDEO_SMALL_CARD_TYPE
}
return FOLLOW_CARD_TYPE
}
}
with(multiTypeDelegate) {
registerItemType(BANNER_TYPE, R.layout.layout_card_banner)
registerItemType(FOLLOW_CARD_TYPE, R.layout.layout_follow_card)
registerItemType(HORIZONTAL_SCROLL_CARD_TYPE, R.layout.layout_horizontal_scroll_card)
registerItemType(VIDEO_COLLECTION_WITH_COVER_TYPE, R.layout.layout_collection_with_cover)
registerItemType(SQUARE_CARD_COLLECTION_TYPE, R.layout.layout_square_collection)
registerItemType(VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE, R.layout.item_collection_of_horizontal_scroll_card)
registerItemType(VIDEO_COLLECTION_WITH_BRIEF_TYPE, R.layout.layout_collection_with_brief)
registerItemType(TEXT_CARD_TYPE, R.layout.layout_single_text)
registerItemType(BRIEF_CARD_TYPE, R.layout.layout_brife_card)
registerItemType(BLANK_CARD_TYPE, R.layout.layout_blank_card)
registerItemType(SQUARE_CARD_TYPE, R.layout.item_square_card)
registerItemType(RECTANGLE_CARD_TYPE, R.layout.item_square_card)
registerItemType(VIDEO_TYPE, R.layout.layout_single_video)
registerItemType(VIDEO_BANNER_THREE_TYPE, R.layout.layout_follow_card)
registerItemType(VIDEO_SMALL_CARD_TYPE, R.layout.layout_video_small_card)
}
//设置倒数第5个开启预加载
setPreLoadNumber(5)
}
override fun convert(helper: BaseViewHolder?, item: Content) {
when (helper?.itemViewType) {
BANNER_TYPE -> setBannerInfo(helper, item)
FOLLOW_CARD_TYPE -> setFollowCardInfo(helper, item)
HORIZONTAL_SCROLL_CARD_TYPE -> setHorizontalScrollCardInfo(helper, item.data.itemList)
VIDEO_COLLECTION_WITH_COVER_TYPE -> setCollectionCardWithCoverInfo(helper, item.data)
SQUARE_CARD_COLLECTION_TYPE -> setSquareCollectionInfo(helper, item.data)
VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE -> setCollectionOfHorizontalScrollCardInfo(helper, item)
VIDEO_COLLECTION_WITH_BRIEF_TYPE -> setCollectionBriefInfo(helper, item)
TEXT_CARD_TYPE -> setSingleText(helper, item)
BRIEF_CARD_TYPE -> setBriefCardInfo(helper, item)
BLANK_CARD_TYPE -> setBlankCardInfo(helper, item)
SQUARE_CARD_TYPE -> setSquareCardInfo(helper, item.data)
RECTANGLE_CARD_TYPE -> setRectangleCardInfo(helper, item.data)
VIDEO_TYPE -> setSingleVideoInfo(helper, item)
VIDEO_BANNER_THREE_TYPE -> setBanner3Info(helper, item.data)
VIDEO_SMALL_CARD_TYPE -> setSmallCardInfo(helper, item.data)
}
}
/**
* 设置视频banner信息
*/
private fun setBannerInfo(helper: BaseViewHolder, item: Content) {
with(helper) {
getView(R.id.iv_image).setImageURI(item.data.image)
itemView.setOnClickListener {
if (!TextUtils.isEmpty(item.data.actionUrl)) {
mContext.startActivity(Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(item.data.actionUrl)
addCategory(Intent.CATEGORY_DEFAULT)
addCategory(Intent.CATEGORY_BROWSABLE)
})
}
}
}
}
/**
* 设置单视频卡片信息
*/
private fun setFollowCardInfo(helper: BaseViewHolder, item: Content) {
with(helper) {
val info = item.data
//设置头布局
info.header?.let { getView(R.id.item_header_view).setHeader(it, info.type) }
getView(R.id.elite_view).apply {
setImageUrl(info.content!!.data.cover.feed)
setDailyVisible(info.content!!.data.library == "DAILY")
setOnClickListener {
//跳转到视频详细界面
VideoDetailActivity.start(mContext!!, item.data.content?.data!!, mData as ArrayList, mData.indexOf(item))
}
}
}
}
/**
* 设置水平滚动卡片集合信息
*/
private fun setCollectionOfHorizontalScrollCardInfo(helper: BaseViewHolder, content: Content) {
with(helper) {
//设置头布局
val data = content.data
data.header?.let { getView(R.id.item_header_view).setHeader(it, content.type) }
//跳转到视频详细界面
getView(R.id.view_pager).apply {
pageViewClickListener = {
val content = data.itemList[it].data
VideoDetailActivity.start(mContext!!, content, data.itemList as ArrayList, it)
}
setData(data.itemList)
}
}
}
/**
* 设置带关注人的视频集合信息
*/
private fun setCollectionBriefInfo(helper: BaseViewHolder, content: Content) {
with(helper) {
getView(R.id.rv_recycler).apply {
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)
adapter = CollectionBriefAdapter(content.data.itemList).apply {
onItemClickListener = OnItemClickListener { _, _, position ->
//跳转到播放视频详情
val item = getItem(position)
VideoDetailActivity.start(mContext, item!!.data, data as ArrayList, position)
}
}
}
//设置头布局
content.data.header?.let { getView(R.id.item_header_view).setHeader(it, content.data.type) }
}
}
/**
* 设置文字信息
*/
private fun setSingleText(helper: BaseViewHolder, item: Content) {
helper.getView(R.id.tv_text).apply {
text = item.data.text
when (item.data.type) {
"header1" -> {
setFontType(item.data.header?.font, FontType.LOBSTER)
gravity = Gravity.CENTER
}
"header2" -> {
setFontType(item.data.header?.font, FontType.BOLD)
gravity = Gravity.CENTER
}
else -> {
gravity = Gravity.START
setFontType(item.data.header?.font, FontType.NORMAL)
}
}
}
}
/**
* 设置合作作者信息
*/
private fun setBriefCardInfo(helper: BaseViewHolder, content: Content) {
helper.getView(R.id.item_header_view).setAuthorHeader(content.data)
}
/**
* 设置空白信息高度
*/
private fun setBlankCardInfo(helper: BaseViewHolder, content: Content) {
helper.getView(R.id.view).apply {
val lp = layoutParams
lp.height = mContext.dip2px(content.data.height.toFloat())
layoutParams = lp
}
}
/**
* 设置banner3(广告信息)信息
*/
private fun setBanner3Info(helper: BaseViewHolder, item: ContentBean) {
with(helper) {
//设置头布局
item.header?.let { getView(R.id.item_header_view).setHeader(it, item.type) }
getView(R.id.elite_view).apply {
setImageUrl(item.image)
setDailyVisible(false)
setTranslateText(mContext.getString(R.string.advert))
}
}
}
/**
* 设置水平滚动卡片信息
*/
private fun setHorizontalScrollCardInfo(helper: BaseViewHolder, itemList: MutableList) {
helper.getView(R.id.banner).apply {
setImageLoader(FrescoImageLoader())
setImages(getHorizonTalCardUrl(itemList))
setBannerStyle(BannerConfig.CIRCLE_INDICATOR)
setIndicatorGravity(BannerConfig.CENTER)
isAutoPlay(true)
start()
setDelayTime(5000)
setOnBannerListener {
//跳转到过滤界面
mContext.startActivity(Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(itemList[it].data.actionUrl)
addCategory(Intent.CATEGORY_DEFAULT)
addCategory(Intent.CATEGORY_BROWSABLE)
})
}
}
}
/**
* 获取水平卡片图片地址
*/
private fun getHorizonTalCardUrl(itemList: MutableList) = itemList.map { it.data.image }
/**
* 设置集合卡片信息
*/
private fun setCollectionCardWithCoverInfo(helper: BaseViewHolder, item: ContentBean) {
with(helper) {
getView(R.id.iv_image).apply {
item.header?.cover?.let { setImageUrl(it) }
setArrowVisible(true)
}
getView(R.id.rv_collection_cover_recycler).apply {
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)
adapter = CollectionCardCoverAdapter(item.itemList)
}
}
}
/**
* 设置矩形卡片信息
*/
private fun setSquareCollectionInfo(helper: BaseViewHolder, item: ContentBean) {
val itemList: MutableList = item.itemList
with(helper) {
getView(R.id.rv_square_recycler).apply {
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)
adapter = SquareCollectionAdapter(itemList).apply {
onItemClickListener = OnItemClickListener { _, _, position ->
ARouter.getInstance()
.build(Uri.parse(itemList[position].data.actionUrl))
.navigation()
}
}
}
item.header?.let { getView(R.id.item_header_view).setHeader(it, item.type) }
}
}
/**
* 设置长方形卡片信息
*/
private fun setSquareCardInfo(helper: BaseViewHolder, item: ContentBean) {
helper.apply {
getView(R.id.iv_simple_image).setImageURI(item.image)
setText(R.id.tv_title, item.title)
}
}
/**
* 设置正方形卡片信息
*/
private fun setRectangleCardInfo(helper: BaseViewHolder, item: ContentBean) {
helper.apply {
getView(R.id.iv_simple_image).setImageURI(item.image)
setText(R.id.tv_title, item.title)
}
}
/**
* 设置单视频信息
*/
private fun setSingleVideoInfo(helper: BaseViewHolder, item: Content) {
with(helper) {
getView(R.id.iv_image).setImageURI(item.data.cover.feed)
setText(R.id.tv_single_title, item.data.title)
val description = "#${item.data.category} / ${getElapseTimeForShow(item.data.duration)}"
setText(R.id.tv_single_desc, description)
//设置label
if (item.data.label != null) {
setGone(R.id.tv_label, true)
setText(R.id.tv_label, item.data.label?.text)
} else {
setGone(R.id.tv_label, false)
}
//点击跳转到视频界面
itemView.setOnClickListener { VideoDetailActivity.start(mContext, item.data, data as ArrayList) }
}
}
/**
* 设置小视频卡片信息
*/
private fun setSmallCardInfo(helper: BaseViewHolder, data: ContentBean) {
with(helper) {
getView(R.id.iv_image).setImageURI(data.cover.feed)
setText(R.id.tv_title, data.title)
val description = "#${data.category} / ${getElapseTimeForShow(data.duration)}"
setText(R.id.tv_desc, description)
itemView.setOnClickListener { VideoDetailActivity.start(mContext, data, arrayListOf()) }
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/model/BaseModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base.model
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.entity.JenniferInfo
import com.jennifer.andy.simpleeyes.net.Api
import com.jennifer.andy.simpleeyes.rx.RxThreadHelper
import com.jennifer.andy.simpleeyes.rx.error.globalHandleError
import io.reactivex.Observable
/**
* Author: andy.xwt
* Date: 2017/9/5 19:08
* Description:
*/
interface BaseModel {
/**
* 加载更多信息,针对于AndyInfo数据类型
*/
fun loadMoreAndyInfo(nextPageUrl: String?): Observable = Api.getDefault()
.getMoreAndyInfo(nextPageUrl)
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 加载更多信息,针对于JenniferInfo数据类型
*/
fun loadMoreJenniferInfo(nextPageUrl: String?): Observable = Api.getDefault()
.getMoreJenniferInfo(nextPageUrl)
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 根据url,获取数据
*/
fun getDataInfoFromUrl(url: String?): Observable = Api.getDefault()
.getDataInfoFromUrl(url)
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/presenter/BasePresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base.presenter
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
/**
* Author: andy.xwt
* Date: 2017/9/5 19:10
* Description:
*/
open class BasePresenter : LifecycleObserver {
protected var mView: V? = null
protected lateinit var mScopeProvider: AndroidLifecycleScopeProvider
/**
* 与view进行关联
*/
fun attachView(view: V, lifecycleOwner: LifecycleOwner) {
this.mView = view
this.mScopeProvider = AndroidLifecycleScopeProvider.from(lifecycleOwner)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/presenter/LoadMorePresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.LoadMoreView
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2019-07-10 22:24
* Description: 加载更多Presenter。
* 第一个泛型参数T:为加载更多对应的数据类型
* 第二个泛型参数M:为对应model
* 第三个泛型参数V:为实现了加载更多的View
*/
open class LoadMorePresenter> : BasePresenter() {
protected var mNextPageUrl: String? = null
protected open lateinit var mBaseModel: M
fun loadMoreInfo() {
if (mNextPageUrl != null) {
mBaseModel.loadMoreAndyInfo(mNextPageUrl).autoDispose(mScopeProvider).subscribe({
mView?.showContent()
if (mNextPageUrl == null) {
mView?.showNoMore()
} else {
mNextPageUrl = it.nextPageUrl
mView?.loadMoreSuccess(it as T)
}
}, {
mView?.showNetError(View.OnClickListener {
loadMoreInfo()
})
})
} else {
mView?.showNoMore()
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.common
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
/**
* Author: andy.xwt
* Date: 2019-08-26 20:37
* Description:
*/
class CommonModel:BaseModel
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.common
import android.view.View
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.presenter.LoadMorePresenter
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2019-08-26 19:51
* Description:
*/
class CommonPresenter : LoadMorePresenter() {
override var mBaseModel = CommonModel()
/**
* 获取专题信息
*/
fun loadDataInfoFromUrl(url: String?) {
mBaseModel.getDataInfoFromUrl(url).autoDispose(mScopeProvider).subscribe({
mView?.showLoadSuccess(it.itemList)
mNextPageUrl = it.nextPageUrl
}, {
mView?.showNetError(View.OnClickListener { loadDataInfoFromUrl(url) })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonRecyclerActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.common
import android.os.Bundle
import android.widget.RelativeLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.CustomLoadMoreView
/**
* Author: andy.xwt
* Date: 2019-08-26 19:47
* Description:公共RecyclerView界面,根据url加载数据
*/
@Route(path = "/AndyJennifer/common")
class CommonRecyclerActivity : BaseActivity(), CommonView {
@Autowired
@JvmField
var title: String? = null
@Autowired
@JvmField
var url: String? = null
private val mToolbar: RelativeLayout by bindView(R.id.tool_bar)
private val mRecycler by bindView(R.id.rv_recycler)
private var mCommonAdapter: BaseDataAdapter? = null
override fun initView(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
initToolBar(mToolbar, title)
mPresenter.loadDataInfoFromUrl(url)
}
override fun showLoadSuccess(itemList: MutableList) {
with(mRecycler) {
mCommonAdapter = BaseDataAdapter(itemList).apply {
setOnLoadMoreListener({ mPresenter.loadMoreInfo() }, mRecycler)
setLoadMoreView(CustomLoadMoreView())
}
layoutManager = LinearLayoutManager(mContext)
adapter = mCommonAdapter
}
}
override fun loadMoreSuccess(data: AndyInfo) {
mCommonAdapter?.addData(data.itemList)
mCommonAdapter?.loadMoreComplete()
}
override fun showNoMore() {
mCommonAdapter?.loadMoreEnd()
}
override fun getContentViewLayoutId() = R.layout.activity_common_recyclerview
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.common
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.base.LoadMoreView
/**
* Author: andy.xwt
* Date: 2019-08-26 19:48
* Description:
*/
interface CommonView: LoadMoreView{
fun showLoadSuccess(itemList: MutableList)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/AllCategoryActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.RelativeLayout
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.chad.library.adapter.base.BaseQuickAdapter
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter.Companion.RECTANGLE_CARD_TYPE
import com.jennifer.andy.simpleeyes.ui.feed.presenter.AllCategoryPresenter
import com.jennifer.andy.simpleeyes.ui.feed.view.AllCategoryView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.GridItemDecoration
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
/**
* Author: andy.xwt
* Date: 2018/7/16 14:17
* Description: 全部分类
*/
class AllCategoryActivity : BaseActivity(), AllCategoryView {
private val mToolBar: RelativeLayout by bindView(R.id.tool_bar)
private val mRecyclerView: RecyclerView by bindView(R.id.rv_recycler)
private val mStateView: MultipleStateView by bindView(R.id.multiple_state_view)
override fun initView(savedInstanceState: Bundle?) {
initToolBar(mToolBar, R.string.all_category)
mPresenter.loadAllCategoriesInfo()
}
override fun loadAllCategoriesSuccess(andyInfo: AndyInfo) {
val adapter = BaseDataAdapter(andyInfo.itemList)
adapter.setSpanSizeLookup { _, position ->
if (adapter.getItemViewType(position) == RECTANGLE_CARD_TYPE) 2 else 1
}
//根据actionUrl跳转到相应界面
adapter.onItemClickListener = BaseQuickAdapter.OnItemClickListener { _, _, position ->
ARouter.getInstance()
.build(Uri.parse(adapter.data[position].data.actionUrl))
.navigation()
}
mRecyclerView.layoutManager = GridLayoutManager(mContext, 2)
mRecyclerView.addItemDecoration(GridItemDecoration(2, 4, true))
mRecyclerView.adapter = adapter
}
override fun showNetError(onClickListener: View.OnClickListener) {
mStateView.showNetError(onClickListener)
}
override fun showContent() {
mStateView.showContent()
}
override fun getContentViewLayoutId() = R.layout.activity_all_category
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/CategoryTabActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.animation.ArgbEvaluator
import android.graphics.Color
import android.os.Bundle
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Category
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.BaseFragmentItemAdapter
import com.jennifer.andy.simpleeyes.ui.feed.presenter.CategoryTabPresenter
import com.jennifer.andy.simpleeyes.ui.feed.view.CategoryTabView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.utils.showKeyboard
import com.jennifer.andy.simpleeyes.widget.StickyNavLayout
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.tab.ShortTabLayout
/**
* Author: andy.xwt
* Date: 2018/8/6 10:46
* Description:种类界面,包括广告、剧情等同类型的分类。
*/
@Route(path = "/AndyJennifer/category")
class CategoryTabActivity : BaseActivity(), CategoryTabView {
private val mStickyNavLayout: StickyNavLayout by bindView(R.id.stick_layout)
private val mViewPager: ViewPager by bindView(R.id.id_sticky_nav_layout_viewpager)
private val mTabLayout: ShortTabLayout by bindView(R.id.id_sticky_nav_layout_nav_view)
private val mImageView: SimpleDraweeView by bindView(R.id.iv_image)
private val mTvSubTitle: CustomFontTextView by bindView(R.id.tv_sub_title)
private val mTvDesc: CustomFontTextView by bindView(R.id.tv_desc)
private val mTvFollow: CustomFontTextView by bindView(R.id.tv_follow)
@Autowired
@JvmField
var title: String? = null
@Autowired
@JvmField
var id: String? = null
@Autowired
@JvmField
var tabIndex: String? = null
override fun initView(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
mPresenter.getTabInfo(id!!)
initToolBar(R.drawable.ic_action_back_white, title, 0f)
}
override fun showLoadTabSuccess(category: Category) {
mImageView.setImageURI(category.categoryInfo.headerImage)
mTvSubTitle.text = category.categoryInfo.name
mTvDesc.text = category.categoryInfo.description
mViewPager.adapter = BaseFragmentItemAdapter(supportFragmentManager, initFragments(category.tabInfo), initTitles(category.tabInfo))
mTabLayout.setupWithViewPager(mViewPager)
mViewPager.currentItem = tabIndex?.toInt() ?: 0
mStickyNavLayout.setScrollChangeListener(object : StickyNavLayout.ScrollChangeListener {
override fun onScroll(moveRatio: Float) {
if (moveRatio < 1) initToolBar(R.drawable.ic_action_back_white, title, moveRatio)
else initToolBar(R.drawable.ic_action_back_black, title, moveRatio)
}
})
}
private fun initFragments(tabInfo: TabInfo): MutableList {
val fragments = mutableListOf()
for (i in tabInfo.tabList.indices) {
fragments.add(TagDetailInfoFragment.newInstance(tabInfo.tabList[i].apiUrl))
}
return fragments
}
private fun initTitles(tabInfo: TabInfo): MutableList {
val titles = mutableListOf()
for (i in tabInfo.tabList.indices) {
titles.add(tabInfo.tabList[i].name)
}
return titles
}
private fun initToolBar(@DrawableRes backResId: Int, title: String? = null, titleAlpha: Float = 1f) {
val ivBack = findViewById(R.id.iv_back)
ivBack.setOnClickListener {
showKeyboard(false)
finish()
}
//设置渐变
val color = ArgbEvaluator().evaluate(titleAlpha, Color.WHITE, Color.BLACK) as Int
val wrapDrawable = DrawableCompat.wrap(getDrawable(backResId))
wrapDrawable.setTint(color)
ivBack.setImageDrawable(wrapDrawable)
val tvTitle = findViewById(R.id.tv_title)
tvTitle.setFontType(fontType = FontType.BOLD)
tvTitle.text = title
tvTitle.alpha = titleAlpha
}
override fun getContentViewLayoutId() = R.layout.activity_category_tab
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/FeedFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseFragment
import com.jennifer.andy.simpleeyes.ui.base.BaseFragmentItemAdapter
import com.jennifer.andy.simpleeyes.ui.feed.presenter.FeedPresenter
import com.jennifer.andy.simpleeyes.ui.feed.view.FeedView
import com.jennifer.andy.simpleeyes.ui.search.SearchHotActivity
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.utils.readyGo
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
import com.jennifer.andy.simpleeyes.widget.tab.ShortTabLayout
/**
* Author: andy.xwt
* Date: 2017/9/22 13:51
* Description:发现
*/
class FeedFragment : BaseFragment(), FeedView {
private val mViewPager: ViewPager by bindView(R.id.view_pager)
private val mTvAllCategory: CustomFontTextView by bindView(R.id.tv_all_category)
private val mIvSearch: ImageView by bindView(R.id.iv_search)
private val mTabLayout: ShortTabLayout by bindView(R.id.tab_layout)
private val mStateView: MultipleStateView by bindView(R.id.multiple_state_view)
companion object {
@JvmStatic
fun newInstance(): FeedFragment = FeedFragment()
}
override fun initView(savedInstanceState: Bundle?) {
mPresenter.getDiscoveryTab()
//跳转到搜索界面
mIvSearch.setOnClickListener {
readyGo()
}
//跳转到全部分类界面
mTvAllCategory.setOnClickListener {
readyGo()
}
}
override fun loadTabSuccess(tabInfo: TabInfo) {
mViewPager.adapter = BaseFragmentItemAdapter(childFragmentManager, initFragments(tabInfo), initTitles(tabInfo))
mViewPager.offscreenPageLimit = tabInfo.tabList.size
mTabLayout.setupWithViewPager(mViewPager)
}
private fun initFragments(tabInfo: TabInfo): MutableList {
val fragments = mutableListOf()
for (i in tabInfo.tabList.indices) {
fragments.add(TagDetailInfoFragment.newInstance(tabInfo.tabList[i].apiUrl))
}
return fragments
}
private fun initTitles(tabInfo: TabInfo): MutableList {
val titles = mutableListOf()
for (i in tabInfo.tabList.indices) {
titles.add(tabInfo.tabList[i].name)
}
return titles
}
override fun showNetError(onClickListener: View.OnClickListener) {
mStateView.showNetError(onClickListener)
}
override fun showContent() {
mStateView.showContent()
}
override fun getContentViewLayoutId() = R.layout.fragment_feed
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/RankListActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.os.Bundle
import android.view.View
import android.widget.RelativeLayout
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.BaseFragmentItemAdapter
import com.jennifer.andy.simpleeyes.ui.feed.presenter.RankListPresenter
import com.jennifer.andy.simpleeyes.ui.feed.view.RankListView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
import com.jennifer.andy.simpleeyes.widget.tab.ShortTabLayout
/**
* Author: andy.xwt
* Date: 2018/7/26 18:05
* Description:排行榜
*/
@Route(path = "/AndyJennifer/ranklist")
class RankListActivity : BaseActivity(), RankListView {
private val mToolbar: RelativeLayout by bindView(R.id.tool_bar)
private val mViewPager: ViewPager by bindView(R.id.view_pager)
private val mTabLayout: ShortTabLayout by bindView(R.id.tab_layout)
private val mStateView: MultipleStateView by bindView(R.id.multiple_state_view)
@Autowired
@JvmField
var tabIndex: String? = null//哇,这里ARouter居然不能支持Int类型
override fun initView(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
initToolBar(mToolbar, R.string.open_eyes_english, FontType.LOBSTER)
mPresenter.getRankListTab()
}
override fun loadTabSuccess(tabInfo: TabInfo) {
mTabLayout.visibility = View.VISIBLE
mViewPager.adapter = BaseFragmentItemAdapter(supportFragmentManager, initFragments(tabInfo), initTitles(tabInfo))
mViewPager.offscreenPageLimit = tabInfo.tabList.size
tabIndex?.let { mViewPager.currentItem = it.toInt() }
mTabLayout.setupWithViewPager(mViewPager)
}
private fun initFragments(tabInfo: TabInfo): MutableList {
val fragments = mutableListOf()
for (i in tabInfo.tabList.indices) {
fragments.add(TagDetailInfoFragment.newInstance(tabInfo.tabList[i].apiUrl))
}
return fragments
}
private fun initTitles(tabInfo: TabInfo): MutableList {
val titles = mutableListOf()
for (i in tabInfo.tabList.indices) {
titles.add(tabInfo.tabList[i].name)
}
return titles
}
override fun showNetError(onClickListener: View.OnClickListener) {
mStateView.showNetError(onClickListener)
}
override fun showContent() {
mStateView.showContent()
}
override fun getContentViewLayoutId() = R.layout.activity_rank
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/TagActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.os.Bundle
import android.widget.RelativeLayout
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.TabDetailInfo
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.net.Api
import com.jennifer.andy.simpleeyes.ui.base.BaseAppCompatActivity
import com.jennifer.andy.simpleeyes.ui.base.BaseFragmentItemAdapter
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.tab.ShortTabLayout
/**
* Author: andy.xwt
* Date: 2018/8/3 14:00
* Description: 360 全景
*/
@Route(path = "/AndyJennifer/tag")
class TagActivity : BaseAppCompatActivity() {
private val mToolbar: RelativeLayout by bindView(R.id.tool_bar)
private val mTabLayout: ShortTabLayout by bindView(R.id.tab_layout)
private val mViewPager: ViewPager by bindView(R.id.view_pager)
@Autowired
@JvmField
var title: String? = null
@Autowired
@JvmField
var id: String? = null
override fun initView(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
initToolBar(mToolbar, title)
initTabInfo()
}
private fun initTabInfo() {
/**
* 这里拼数据,是因为抓包的时候,没有发现获取tab信息的接口,所以自己拼的数据
*/
val tabInfoDetailList = mutableListOf()
tabInfoDetailList.add(TabDetailInfo(0, "按时间排序", "${Api.BASE_URL}api/v3/tag/videos?tagId=$id&strategy=date"))
tabInfoDetailList.add(TabDetailInfo(1, "按分享排序", "${Api.BASE_URL}api/v3/tag/videos?tagId=$id&strategy=shareCount"))
val tabInfo = TabInfo(tabInfoDetailList, 0)
mViewPager.adapter = BaseFragmentItemAdapter(supportFragmentManager, initFragments(tabInfo), initTitles(tabInfo))
mViewPager.offscreenPageLimit = tabInfo.tabList.size
mTabLayout.setupWithViewPager(mViewPager)
}
private fun initFragments(tabInfo: TabInfo): MutableList {
val fragments = mutableListOf()
for (i in tabInfo.tabList.indices) {
fragments.add(TagDetailInfoFragment.newInstance(tabInfo.tabList[i].apiUrl))
}
return fragments
}
private fun initTitles(tabInfo: TabInfo): MutableList {
val titles = mutableListOf()
for (i in tabInfo.tabList.indices) {
titles.add(tabInfo.tabList[i].name)
}
return titles
}
override fun getContentViewLayoutId() = R.layout.activity_tag
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/TagDetailInfoFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.net.Extras
import com.jennifer.andy.simpleeyes.ui.base.BaseFragment
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.ui.feed.presenter.TagDetailInfoPresenter
import com.jennifer.andy.simpleeyes.ui.feed.view.TagDetailInfoView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.CustomLoadMoreView
/**
* Author: andy.xwt
* Date: 2018/7/3 11:29
* Description:详细的Tab界面
*/
class TagDetailInfoFragment : BaseFragment(), TagDetailInfoView {
private val mRecyclerView: RecyclerView by bindView(R.id.rv_recycler)
private var mAdapter: BaseDataAdapter? = null
private lateinit var mApiUrl: String
companion object {
@JvmStatic
fun newInstance(apiUrl: String): TagDetailInfoFragment {
val categoryFragment = TagDetailInfoFragment()
val bundle = Bundle()
bundle.putString(Extras.API_URL, apiUrl)
categoryFragment.arguments = bundle
return categoryFragment
}
}
override fun getBundleExtras(extras: Bundle) {
mApiUrl = extras.getString(Extras.API_URL)
}
override fun onLazyInitView(savedInstanceState: Bundle?) {
mPresenter.getDetailInfo(mApiUrl)
}
override fun showGetTabInfoSuccess(andyInfo: AndyInfo) {
if (mAdapter == null) {
mAdapter = BaseDataAdapter(andyInfo.itemList)
mAdapter?.setLoadMoreView(CustomLoadMoreView())
mAdapter?.setOnLoadMoreListener({ mPresenter.loadMoreInfo() }, mRecyclerView)
mRecyclerView.adapter = mAdapter
mRecyclerView.layoutManager = LinearLayoutManager(context)
} else {
mAdapter?.setNewData(andyInfo.itemList)
}
}
override fun loadMoreSuccess(data: AndyInfo) {
mAdapter?.addData(data.itemList)
mAdapter?.loadMoreComplete()
}
override fun showNoMore() {
mAdapter?.loadMoreEnd()
}
override fun getContentViewLayoutId() = R.layout.fragment_tag_detail_info
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/TopicActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.os.Bundle
import android.view.View
import android.widget.RelativeLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.ui.feed.presenter.TopicPresenter
import com.jennifer.andy.simpleeyes.ui.feed.view.TopicView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.CustomLoadMoreView
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
/**
* Author: andy.xwt
* Date: 2018/7/31 15:37
* Description:专题
*/
@Route(path = "/campaign/list")
class TopicActivity : BaseActivity(), TopicView {
private val mToolbar: RelativeLayout by bindView(R.id.tool_bar)
private val mRecyclerView: RecyclerView by bindView(R.id.rv_recycler)
private val mStateView: MultipleStateView by bindView(R.id.multiple_state_view)
private var mAdapter: BaseDataAdapter? = null
@Autowired
@JvmField
var title: String? = null
override fun initView(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
initToolBar(mToolbar, title)
mPresenter.getTopicInfo()
}
override fun showGetTopicInfoSuccess(itemList: MutableList) {
if (mAdapter == null) {
mAdapter = BaseDataAdapter(itemList)
mAdapter?.setOnLoadMoreListener({ mPresenter.loadMoreInfo() }, mRecyclerView)
mAdapter?.setLoadMoreView(CustomLoadMoreView())
mRecyclerView.adapter = mAdapter
mRecyclerView.layoutManager = LinearLayoutManager(mContext)
} else {
mAdapter?.setNewData(itemList)
}
}
override fun loadMoreSuccess(data: AndyInfo) {
mAdapter?.addData(data.itemList)
mAdapter?.loadMoreComplete()
}
override fun showNoMore() {
mAdapter?.loadMoreEnd()
}
override fun showNetError(onClickListener: View.OnClickListener) {
mStateView.showNetError(onClickListener)
}
override fun getContentViewLayoutId() = R.layout.activity_topic
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/WebViewActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed
import android.os.Build
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.ImageView
import android.widget.RelativeLayout
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.ui.base.BaseAppCompatActivity
import com.jennifer.andy.simpleeyes.utils.bindView
/**
* Author: andy.xwt
* Date: 2018/7/20 16:24
* Description:网页展示(设置了标题,设置了能否分享)
* jvmField注解注释的不会生成get与set方法。且为public
*/
@Route(path = "/AndyJennifer/webview/")
class WebViewActivity : BaseAppCompatActivity() {
private val mToolbar: RelativeLayout by bindView(R.id.tool_bar)
private val mIvShare: ImageView by bindView(R.id.iv_share)
private val mWebView: WebView by bindView(R.id.web_view)
@Autowired
@JvmField
var title: String? = null
@Autowired
@JvmField
var url: String? = null
@Autowired
@JvmField
var nid: String? = null
@Autowired
@JvmField
var tid: String? = null
@Autowired
@JvmField
var cookie: String? = null
@Autowired
@JvmField
var udid: String? = null
@Autowired
@JvmField
var shareable: Boolean? = false
override fun initView(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
initToolBar(mToolbar, title)
initWebView()
}
private fun initWebView() {
mWebView.apply {
settings.apply {
javaScriptEnabled = true
loadsImagesAutomatically = true
defaultTextEncodingName = "utf-8"
//5.0以上需要支持混合模式
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
}
}
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?) = false
}
}.loadUrl(url)
}
override fun getContentViewLayoutId() = R.layout.activity_webview
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/model/FeedModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.model
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.entity.Category
import com.jennifer.andy.simpleeyes.entity.Tab
import com.jennifer.andy.simpleeyes.net.Api
import com.jennifer.andy.simpleeyes.rx.RxThreadHelper
import com.jennifer.andy.simpleeyes.rx.error.globalHandleError
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
import io.reactivex.Observable
/**
* Author: andy.xwt
* Date: 2017/9/22 13:52
* Description:
*/
class FeedModel : BaseModel {
/**
* 获取发现tab栏
*/
fun getDiscoveryTab(): Observable =
Api.getDefault()
.getDiscoveryTab()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 获取全部分类信息
*/
fun loadAllCategoriesInfo(): Observable =
Api.getDefault()
.getAllCategoriesInfo()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 获取排行榜tab栏
*/
fun getRankListTab(): Observable =
Api.getDefault()
.getRankListTab()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 获取专题信息
*/
fun getTopicInfo(): Observable =
Api.getDefault()
.getTopicInfo()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 获取种类下tab信息
*/
fun getCategoryTabIno(id: String): Observable =
Api.getDefault()
.getCategoryTabInfo(id)
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/AllCategoryPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.feed.model.FeedModel
import com.jennifer.andy.simpleeyes.ui.feed.view.AllCategoryView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2018/7/16 14:18
* Description:
*/
class AllCategoryPresenter : BasePresenter() {
private var mFeedModel: FeedModel = FeedModel()
fun loadAllCategoriesInfo() {
mFeedModel.loadAllCategoriesInfo().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mView?.loadAllCategoriesSuccess(it)
}, {
mView?.showNetError(View.OnClickListener { loadAllCategoriesInfo() })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/CategoryTabPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.feed.model.FeedModel
import com.jennifer.andy.simpleeyes.ui.feed.view.CategoryTabView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2018/8/6 10:48
* Description:
*/
class CategoryTabPresenter : BasePresenter() {
private var mFeedModel: FeedModel = FeedModel()
fun getTabInfo(id: String) {
mFeedModel.getCategoryTabIno(id).autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mView?.showLoadTabSuccess(it)
}, {
mView?.showNetError(View.OnClickListener { getTabInfo(id) })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/FeedPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.feed.model.FeedModel
import com.jennifer.andy.simpleeyes.ui.feed.view.FeedView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2017/9/22 13:52
* Description:
*/
class FeedPresenter : BasePresenter() {
private var mFeedModel: FeedModel = FeedModel()
/**
* 获取导航栏信息
*/
fun getDiscoveryTab() {
mFeedModel.getDiscoveryTab().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mView?.loadTabSuccess(it.tabInfo)
}, {
mView?.showNetError(View.OnClickListener { getDiscoveryTab() })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/RankListPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.feed.model.FeedModel
import com.jennifer.andy.simpleeyes.ui.feed.view.RankListView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2018/7/26 18:44
* Description:
*/
class RankListPresenter : BasePresenter() {
private var mFeedModel: FeedModel = FeedModel()
fun getRankListTab() {
mFeedModel.getRankListTab().autoDispose(mScopeProvider).subscribe({
mView?.loadTabSuccess(it.tabInfo)
}, {
mView?.showNetError(View.OnClickListener { getRankListTab() })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/TagDetailInfoPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.presenter.LoadMorePresenter
import com.jennifer.andy.simpleeyes.ui.feed.model.FeedModel
import com.jennifer.andy.simpleeyes.ui.feed.view.TagDetailInfoView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2018/7/3 11:30
* Description:
*/
class TagDetailInfoPresenter : LoadMorePresenter() {
override var mBaseModel: FeedModel = FeedModel()
/**
* 获取tab栏下信息
*/
fun getDetailInfo(url: String) {
mBaseModel.getDataInfoFromUrl(url).autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mView?.showGetTabInfoSuccess(it)
mNextPageUrl = it.nextPageUrl
if (mNextPageUrl == null) mView?.showNoMore()
}, {
mView?.showNetError(View.OnClickListener { getDetailInfo(url) })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/TopicPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.presenter.LoadMorePresenter
import com.jennifer.andy.simpleeyes.ui.feed.model.FeedModel
import com.jennifer.andy.simpleeyes.ui.feed.view.TopicView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2018/7/31 15:40
* Description:
*/
class TopicPresenter : LoadMorePresenter() {
override var mBaseModel: FeedModel = FeedModel()
/**
* 获取专题信息
*/
fun getTopicInfo() {
mBaseModel.getTopicInfo().autoDispose(mScopeProvider).subscribe({
mView?.showGetTopicInfoSuccess(it.itemList)
mNextPageUrl = it.nextPageUrl
}, {
mView?.showNetError(View.OnClickListener { getTopicInfo() })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/AllCategoryView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.view
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2018/7/16 14:18
* Description:
*/
interface AllCategoryView : BaseView {
fun loadAllCategoriesSuccess(andyInfo: AndyInfo)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/CategoryTabView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.view
import com.jennifer.andy.simpleeyes.entity.Category
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2018/8/6 10:47
* Description:
*/
interface CategoryTabView : BaseView {
/**
* 加载分类标签成功
*/
fun showLoadTabSuccess(category: Category)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/FeedView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.view
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2017/9/22 13:53
* Description:
*/
interface FeedView : BaseView {
fun loadTabSuccess(tabInfo: TabInfo)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/RankListView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.view
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2018/7/26 18:06
* Description:
*/
interface RankListView : BaseView {
fun loadTabSuccess(tabInfo: TabInfo)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/TagDetailInfoView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.view
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.LoadMoreView
/**
* Author: andy.xwt
* Date: 2018/7/3 11:30
* Description:
*/
interface TagDetailInfoView : LoadMoreView {
fun showGetTabInfoSuccess(andyInfo: AndyInfo)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/TopicView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.feed.view
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.base.LoadMoreView
/**
* Author: andy.xwt
* Date: 2018/7/31 15:38
* Description:
*/
interface TopicView : LoadMoreView {
fun showGetTopicInfoSuccess(itemList: MutableList)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/AllAuthorActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.follow
import android.os.Bundle
import android.view.View
import android.widget.RelativeLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.ui.follow.presenter.AllAuthorPresenter
import com.jennifer.andy.simpleeyes.ui.follow.view.AllAuthorView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.CustomLoadMoreView
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
/**
* Author: andy.xwt
* Date: 2019-07-09 18:22
* Description: 全部作者
*/
class AllAuthorActivity : BaseActivity(), AllAuthorView {
private val mToolBar: RelativeLayout by bindView(R.id.tool_bar)
private val mStateView: MultipleStateView by bindView(R.id.multiple_state_view)
private val mRecyclerView: RecyclerView by bindView(R.id.rv_recycler)
private var mAdapter: BaseDataAdapter? = null
override fun initView(savedInstanceState: Bundle?) {
initToolBar(mToolBar, R.string.all_author)
mPresenter.getAllAuthorInfo()
}
override fun loadAllAuthorSuccess(it: AndyInfo) {
mAdapter = BaseDataAdapter(it.itemList)
mAdapter?.setLoadMoreView(CustomLoadMoreView())
mAdapter?.setOnLoadMoreListener({ mPresenter.loadMoreInfo() }, mRecyclerView)
mRecyclerView.adapter = mAdapter
mRecyclerView.layoutManager = LinearLayoutManager(this)
}
override fun showNetError(onClickListener: View.OnClickListener) {
mStateView.showNetError(onClickListener)
}
override fun showContent() {
mStateView.showContent()
}
override fun loadMoreSuccess(data: AndyInfo) {
mAdapter?.addData(data.itemList)
mAdapter?.loadMoreComplete()
}
override fun showNoMore() {
mAdapter?.loadMoreEnd()
}
override fun getContentViewLayoutId() = R.layout.activity_all_author
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/FollowFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.follow
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.recyclerview.widget.LinearLayoutManager
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseFragment
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.ui.follow.presenter.FollowPresenter
import com.jennifer.andy.simpleeyes.ui.follow.view.FollowView
import com.jennifer.andy.simpleeyes.ui.search.SearchHotActivity
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.utils.readyGo
import com.jennifer.andy.simpleeyes.widget.CustomLoadMoreView
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.pull.refresh.PullToRefreshRecyclerView
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
/**
* Author: andy.xwt
* Date: 2017/9/22 13:51
* Description: 关注
*/
class FollowFragment : BaseFragment(), FollowView {
private val mTvAllAuthor: CustomFontTextView by bindView(R.id.tv_all_author)
private val mIvSearch: ImageView by bindView(R.id.iv_search)
private val mStateView: MultipleStateView by bindView(R.id.multiple_state_view)
private val mRecyclerView: PullToRefreshRecyclerView by bindView(R.id.rv_follow_recycler)
private var mAdapter: BaseDataAdapter? = null
companion object {
@JvmStatic
fun newInstance(): FollowFragment = FollowFragment()
}
override fun initView(savedInstanceState: Bundle?) {
//获取主界面信息
mPresenter.getFollowInfo()
//跳转到搜索界面
mIvSearch.setOnClickListener {
readyGo()
}
//跳转到全部作者界面
mTvAllAuthor.setOnClickListener {
readyGo()
}
mRecyclerView.refreshListener = { mPresenter.refresh() }
}
override fun loadFollowInfoSuccess(andyInfo: AndyInfo) {
if (mAdapter == null) {
mAdapter = BaseDataAdapter(andyInfo.itemList)
mAdapter?.setLoadMoreView(CustomLoadMoreView())
mAdapter?.setOnLoadMoreListener({ mPresenter.loadMoreInfo() }, mRecyclerView.rootView)
mRecyclerView.setAdapterAndLayoutManager(mAdapter!!, LinearLayoutManager(context))
} else {
mAdapter?.setNewData(andyInfo.itemList)
}
}
override fun showNetError(onClickListener: View.OnClickListener) {
mStateView.showNetError(onClickListener)
}
override fun showContent() {
mStateView.showContent()
}
override fun loadMoreSuccess(data: AndyInfo) {
mAdapter?.addData(data.itemList)
mAdapter?.loadMoreComplete()
}
override fun showNoMore() {
mAdapter?.loadMoreEnd()
}
override fun refreshSuccess(andyInfo: AndyInfo) {
loadFollowInfoSuccess(andyInfo)
mRecyclerView.refreshComplete()
}
override fun getContentViewLayoutId() = R.layout.fragment_follow
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/model/FollowModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.follow.model
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.net.Api
import com.jennifer.andy.simpleeyes.rx.RxThreadHelper
import com.jennifer.andy.simpleeyes.rx.error.globalHandleError
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
import io.reactivex.Observable
/**
* Author: andy.xwt
* Date: 2017/9/22 13:54
* Description:
*/
class FollowModel : BaseModel {
/**
* 获取关注首页信息
*/
fun getFollowInfo(): Observable =
Api.getDefault()
.getFollowInfo()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 获取全部作者
*/
fun getAllAuthor(): Observable =
Api.getDefault()
.getAllAuthor()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/presenter/AllAuthorPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.follow.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.presenter.LoadMorePresenter
import com.jennifer.andy.simpleeyes.ui.follow.model.FollowModel
import com.jennifer.andy.simpleeyes.ui.follow.view.AllAuthorView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2019-07-09 18:23
* Description:
*/
class AllAuthorPresenter : LoadMorePresenter() {
override var mBaseModel: FollowModel = FollowModel()
fun getAllAuthorInfo() {
mBaseModel.getAllAuthor().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mNextPageUrl = it.nextPageUrl
mView?.loadAllAuthorSuccess(it)
}, {
mView?.showNetError(View.OnClickListener { getAllAuthorInfo() })
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/presenter/FollowPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.follow.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.presenter.LoadMorePresenter
import com.jennifer.andy.simpleeyes.ui.follow.model.FollowModel
import com.jennifer.andy.simpleeyes.ui.follow.view.FollowView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2017/9/22 13:54
* Description:
*/
class FollowPresenter : LoadMorePresenter() {
override var mBaseModel: FollowModel = FollowModel()
fun getFollowInfo() {
mBaseModel.getFollowInfo().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mNextPageUrl = it.nextPageUrl
mView?.loadFollowInfoSuccess(it)
}, {
mView?.showNetError(View.OnClickListener { getFollowInfo() })
})
}
fun refresh() {
mBaseModel.getFollowInfo().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mNextPageUrl = it.nextPageUrl
mView?.refreshSuccess(it)
}, {
mView?.showNetError(View.OnClickListener {
refresh()
})
})
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/view/AllAuthorView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.follow.view
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.LoadMoreView
/**
* Author: andy.xwt
* Date: 2019-07-09 18:22
* Description:
*/
interface AllAuthorView : LoadMoreView {
fun loadAllAuthorSuccess(it: AndyInfo)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/view/FollowView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.follow.view
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.LoadMoreView
/**
* Author: andy.xwt
* Date: 2017/9/22 13:54
* Description:
*/
interface FollowView : LoadMoreView {
fun loadFollowInfoSuccess(andyInfo: AndyInfo)
fun refreshSuccess(andyInfo: AndyInfo)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/DailyEliteActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.ui.home.adapter.DailyEliteAdapter
import com.jennifer.andy.simpleeyes.ui.home.presenter.DailyElitePresenter
import com.jennifer.andy.simpleeyes.ui.home.view.DailyEliteView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.pull.refresh.LinearLayoutManagerWithSmoothScroller
import com.jennifer.andy.simpleeyes.widget.pull.refresh.PullToRefreshRecyclerView
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
/**
* Author: andy.xwt
* Date: 2018/4/20 16:09
* Description:每日编辑精选
*/
class DailyEliteActivity : BaseActivity(), DailyEliteView {
private val mBackImageView: ImageView by bindView(R.id.iv_back)
private val mRecycler: PullToRefreshRecyclerView by bindView(R.id.rv_recycler)
private val mTvDate: CustomFontTextView by bindView(R.id.tv_date)
private val mStateView: MultipleStateView by bindView(R.id.multiple_state_view)
private var mDailyEliteAdapter: DailyEliteAdapter? = null
private lateinit var mLinearLayoutManager: LinearLayoutManagerWithSmoothScroller
override fun initView(savedInstanceState: Bundle?) {
mPresenter.getDailyElite()
mRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
//设置当前选择日期
val position = mLinearLayoutManager.findFirstVisibleItemPosition()
if (mDailyEliteAdapter?.getItemViewType(position) == BaseDataAdapter.TEXT_CARD_TYPE) {
mTvDate.text = mDailyEliteAdapter?.getItem(position)?.data?.text
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
val layoutManager = mRecycler.rootView.layoutManager as LinearLayoutManager
//滚动到日期及完毕
if (newState == RecyclerView.SCROLL_STATE_IDLE && layoutManager.findFirstVisibleItemPosition() == mDailyEliteAdapter!!.getCurrentDayPosition()) {
mRecycler.refreshComplete()
}
}
})
mRecycler.refreshListener = { mPresenter.refresh() }
mBackImageView.setOnClickListener { finish() }
}
override fun showGetDailySuccess(content: MutableList) {
if (mDailyEliteAdapter == null) {
mDailyEliteAdapter = DailyEliteAdapter(content)
mDailyEliteAdapter?.setOnLoadMoreListener({ mPresenter.loadMoreResult() }, mRecycler.rootView)
mLinearLayoutManager = LinearLayoutManagerWithSmoothScroller(mContext)
mRecycler.setAdapterAndLayoutManager(mDailyEliteAdapter!!, mLinearLayoutManager)
} else {
mDailyEliteAdapter?.setNewData(content)
}
//这里有可能加载的每日精选,没有text类型,就会导致刷新完毕后,刷新界面没有消失
if (mDailyEliteAdapter!!.getCurrentDayPosition() != 0) {
//这里发送延时消息,是因为数据还有可能没有装载完毕
mRecycler.handler.postDelayed({ mRecycler.smoothScrollToPosition(mDailyEliteAdapter!!.getCurrentDayPosition()) }, 200)
} else {
mRecycler.refreshComplete()
}
}
override fun showRefreshSuccess(content: MutableList) {
showGetDailySuccess(content)
}
override fun loadMoreSuccess(data: MutableList) {
mDailyEliteAdapter?.addData(data)
mDailyEliteAdapter?.loadMoreComplete()
}
override fun showNoMore() {
mDailyEliteAdapter?.loadMoreEnd()
}
override fun showNetError(onClickListener: View.OnClickListener) {
mStateView.showNetError(onClickListener)
}
override fun getContentViewLayoutId() = R.layout.activity_daily_elite
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/HomeFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home
import android.os.Bundle
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseFragment
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
import com.jennifer.andy.simpleeyes.ui.home.presenter.HomePresenter
import com.jennifer.andy.simpleeyes.ui.home.view.HomeView
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.utils.getScreenHeight
import com.jennifer.andy.simpleeyes.utils.getScreenWidth
import com.jennifer.andy.simpleeyes.widget.CustomLoadMoreView
import com.jennifer.andy.simpleeyes.widget.pull.head.HomePageHeaderView
import com.jennifer.andy.simpleeyes.widget.pull.zoom.PullToZoomBase
import com.jennifer.andy.simpleeyes.widget.pull.zoom.PullToZoomRecyclerView
/**
* Author: andy.xwt
* Date: 2017/9/19 18:45
* Description:首页
*/
class HomeFragment : BaseFragment(), HomeView {
private val mPullToZoomRecycler: PullToZoomRecyclerView by bindView(R.id.rv_home_recycler)
private lateinit var mHomePageHeaderView: HomePageHeaderView
private var mCateGoryAdapter: BaseDataAdapter? = null
companion object {
@JvmStatic
fun newInstance(): HomeFragment = HomeFragment()
}
override fun initView(savedInstanceState: Bundle?) {
mPullToZoomRecycler.setOnPullZoomListener(object : PullToZoomBase.OnPullZoomListener {
override fun onPullZooming(scrollValue: Int) {
mHomePageHeaderView.showRefreshCover(scrollValue)
}
override fun onPullZoomEnd() {
if (mHomePageHeaderView.judgeCanRefresh()) {//只有达到刷新的阀值,上升才刷新,其他情况不刷新
mPresenter.refreshCategoryData()
} else {
mHomePageHeaderView.hideRefreshCover()
}
}
})
mPresenter.loadCategoryData()
}
override fun loadDataSuccess(andyInfo: AndyInfo) {
if (mCateGoryAdapter == null) {
setHeaderInfo(andyInfo)
setAdapterAndListener(andyInfo)
} else {
mCateGoryAdapter?.setNewData(andyInfo.itemList)
}
}
private fun setAdapterAndListener(andyInfo: AndyInfo) {
val recyclerView = mPullToZoomRecycler.getPullRootView()
recyclerView.setItemViewCacheSize(10)
mCateGoryAdapter = BaseDataAdapter(andyInfo.itemList)
mCateGoryAdapter?.setOnLoadMoreListener({ mPresenter.loadMoreCategoryData() }, recyclerView)
mCateGoryAdapter?.setLoadMoreView(CustomLoadMoreView())
mPullToZoomRecycler.setAdapterAndLayoutManager(mCateGoryAdapter!!, LinearLayoutManager(_mActivity))
}
private fun setHeaderInfo(andyInfo: AndyInfo) {
mHomePageHeaderView = HomePageHeaderView(context!!)
val lp = ViewGroup.LayoutParams(context!!.getScreenWidth(), context!!.getScreenHeight() / 2)
mPullToZoomRecycler.setHeaderViewLayoutParams(LinearLayout.LayoutParams(lp))
mHomePageHeaderView.setHeaderInfo(andyInfo.topIssue, andyInfo.topIssue.data.itemList, this)
mPullToZoomRecycler.setHeaderView(mHomePageHeaderView)
}
override fun refreshDataSuccess(andyInfo: AndyInfo) {
mCateGoryAdapter?.removeAllFooterView()
mCateGoryAdapter?.setNewData(andyInfo.itemList)
mHomePageHeaderView.hideRefreshCover()
}
override fun loadMoreSuccess(andyInfo: AndyInfo) {
mCateGoryAdapter?.addData(andyInfo.itemList)
mCateGoryAdapter?.loadMoreComplete()
}
override fun showNoMore() {
mCateGoryAdapter?.loadMoreEnd()
}
fun scrollToTop() {
mPullToZoomRecycler.scrollToTop()
}
override fun getContentViewLayoutId() = R.layout.fragment_home
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/adapter/CollectionCardCoverAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.adapter
import android.widget.TextView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.utils.getElapseTimeForShow
/**
* Author: andy.xwt
* Date: 2017/11/6 16:14
* Description:集合卡片适配器
*/
class CollectionCardCoverAdapter(data: MutableList) : BaseQuickAdapter(R.layout.item_collection_card_cover, data) {
override fun convert(helper: BaseViewHolder, item: Content) {
val imageCover = helper.getView(R.id.iv_image)
val title = helper.getView(R.id.tv_title)
val desc = helper.getView(R.id.tv_desc)
helper.setGone(R.id.iv_daily, item.data.library == "DAILY")
if (item.type != "actionCard") {//集合
imageCover.setImageURI(item.data.cover.feed)
title.text = item.data.title
val description = "#${item.data.category} / ${getElapseTimeForShow(item.data.duration)}"
val elite = if (item.data.library == "DAILY")
" / ${mContext.getString(R.string.elite)}" else ""
desc.text = description + elite
} else {//显示全部
helper.setGone(R.id.tv_show_all, true)
helper.setGone(R.id.ll_container, false)
helper.addOnClickListener(R.id.tv_show_all)
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/adapter/DailyEliteAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.adapter
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.base.adapter.BaseDataAdapter
/**
* Author: andy.xwt
* Date: 2018/4/20 17:04
* Description:每日编辑精选
*/
class DailyEliteAdapter(data: MutableList) : BaseDataAdapter(data) {
/**
* 获取第一个日期位置
*/
fun getCurrentDayPosition(): Int {
for (i in 0 until mData.size) {
if (getItemViewType(i) == TEXT_CARD_TYPE) {
return i
}
}
return 0
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/adapter/SquareCollectionAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.adapter
import android.widget.FrameLayout
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.font.TypefaceManager
/**
* Author: andy.xwt
* Date: 2017/11/6 10:49
* Description:矩形卡片适配器
*/
class SquareCollectionAdapter(data: MutableList) : BaseQuickAdapter(R.layout.item_square_collection, data) {
override fun convert(helper: BaseViewHolder, item: Content) {
val imageView = helper.getView(R.id.iv_simple_image)
val coverView = helper.getView(R.id.fl_cover)
if (item.type != "actionCard") {//分类
imageView.setImageURI(item.data.image)
helper.setText(R.id.tv_title, item.data.title)
coverView.foreground = mContext?.resources?.getDrawable(R.drawable.selector_item_square_foreground,null)
} else {//显示全部
imageView.setImageDrawable(mContext.resources.getDrawable(R.drawable.shape_show_all_border,null))
helper.setText(R.id.tv_title, item.data.text)
helper.setTextColor(R.id.tv_title, mContext.resources.getColor(R.color.black))
helper.setTypeface(R.id.tv_title, TypefaceManager.getTypeFace(FontType.NORMAL))
coverView.foreground = null
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/model/HomeModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.model
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.entity.JenniferInfo
import com.jennifer.andy.simpleeyes.net.Api
import com.jennifer.andy.simpleeyes.rx.RxThreadHelper
import com.jennifer.andy.simpleeyes.rx.error.globalHandleError
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
import io.reactivex.Observable
import java.util.concurrent.TimeUnit
/**
* Author: andy.xwt
* Date: 2017/9/19 18:00
* Description:
*/
class HomeModel : BaseModel {
/**
* 加载首页信息
*/
fun loadCategoryInfo(): Observable =
Api.getDefault()
.getHomeInfo()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 刷新主页信息,延迟1秒执行
*/
fun refreshCategoryInfo(): Observable =
Api.getDefault()
.getHomeInfo()
.delay(1000, TimeUnit.MILLISECONDS)
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 热门关键词获取
*/
fun getHotWord(): Observable> =
Api.getDefault()
.getHotWord()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 根据关键字搜索视频
*/
fun searchVideoByWord(word: String): Observable =
Api.getDefault()
.searchVideoByWord(word)
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
/**
* 获取每日编辑精选
*/
fun getDailyElite(): Observable =
Api.getDefault()
.getDailyElite()
.compose(globalHandleError())
.compose(RxThreadHelper.switchObservableThread())
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/presenter/DailyElitePresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.entity.JenniferInfo
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.home.model.HomeModel
import com.jennifer.andy.simpleeyes.ui.home.view.DailyEliteView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2018/4/20 16:12
* Description:
*/
class DailyElitePresenter : BasePresenter() {
private var mHomeModel: HomeModel = HomeModel()
private var mNextPageUrl: String? = null
fun getDailyElite() {
mHomeModel.getDailyElite().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mNextPageUrl = it.nextPageUrl
mView?.showGetDailySuccess(combineContentInfo(it))
}, {
mView?.showNetError(View.OnClickListener {
getDailyElite()
})
})
}
/**
* 刷新
*/
fun refresh() {
mHomeModel.getDailyElite().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mNextPageUrl = it.nextPageUrl
mView?.showRefreshSuccess(combineContentInfo(it))
}, {
mView?.showNetError(View.OnClickListener {
refresh()
})
})
}
/**
* 加載更多
*/
fun loadMoreResult() {
mHomeModel.loadMoreJenniferInfo(mNextPageUrl).autoDispose(mScopeProvider).subscribe({
mNextPageUrl = it.nextPageUrl
mView?.loadMoreSuccess(combineContentInfo(it))
}, {
mView?.showNetError(View.OnClickListener {
getDailyElite()
})
})
}
private fun combineContentInfo(jenniferInfo: JenniferInfo): MutableList {
val list = mutableListOf()
val issueList = jenniferInfo.issueList
for (contentBean in issueList) {
val itemList = contentBean.itemList
for (content in itemList) {
list.add(content)
}
}
return list
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/presenter/HomePresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.presenter
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.home.model.HomeModel
import com.jennifer.andy.simpleeyes.ui.home.view.HomeView
import com.uber.autodispose.autoDispose
/**
* Author: andy.xwt
* Date: 2017/9/19 17:58
* Description:
*/
class HomePresenter : BasePresenter() {
private var mHomeModel: HomeModel = HomeModel()
private var mNextPageUrl: String? = null
/**
* 加载首页信息
*/
fun loadCategoryData() {
mView?.showLoading()
mHomeModel.loadCategoryInfo().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mNextPageUrl = it.nextPageUrl
mView?.loadDataSuccess(it)
}, {
mView?.showNetError(View.OnClickListener {
loadCategoryData()
})
})
}
/**
* 刷新首页信息延迟1秒执行
*/
fun refreshCategoryData() {
mHomeModel.refreshCategoryInfo().autoDispose(mScopeProvider).subscribe({
mView?.showContent()
mNextPageUrl = it.nextPageUrl
mView?.refreshDataSuccess(it)
}, {
mView?.showNetError(View.OnClickListener {
refreshCategoryData()
})
})
}
/**
* 加载更多首页信息
*/
fun loadMoreCategoryData() {
if (mNextPageUrl != null) {
mHomeModel.loadMoreAndyInfo(mNextPageUrl).autoDispose(mScopeProvider).subscribe({
mView?.showContent()
if (it.nextPageUrl == null) {
mView?.showNoMore()
} else {
mNextPageUrl = it.nextPageUrl
mView?.loadMoreSuccess(it)
}
}, {
mView?.showNetError(View.OnClickListener {
loadMoreCategoryData()
})
})
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/view/DailyEliteView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.view
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.base.LoadMoreView
/**
* Author: andy.xwt
* Date: 2018/4/20 16:13
* Description:
*/
interface DailyEliteView : LoadMoreView> {
fun showGetDailySuccess(content: MutableList)
fun showRefreshSuccess(content: MutableList)
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/view/HomeView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.home.view
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2017/9/19 17:59
* Description:
*/
interface HomeView : BaseView {
/**
* 加载信息成功
*/
fun loadDataSuccess(andyInfo: AndyInfo)
/**
* 加载信息成功
*/
fun refreshDataSuccess(andyInfo: AndyInfo)
/**
* 加载更多成功
*/
fun loadMoreSuccess(andyInfo: AndyInfo)
/**
* 没有更多
*/
fun showNoMore()
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/login/LoginActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.login
import android.os.Bundle
import android.widget.ImageView
import com.alibaba.android.arouter.facade.annotation.Route
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.ui.base.BaseAppCompatActivity
import com.jennifer.andy.simpleeyes.utils.bindView
/**
* Author: andy.xwt
* Date: 2019/1/14 19:22
* Description:
*/
@Route(path = "/github/Login")
class LoginActivity : BaseAppCompatActivity() {
private val mIvClose: ImageView by bindView(R.id.iv_close)
override fun initView(savedInstanceState: Bundle?) {
mIvClose.setOnClickListener { finish() }
}
override fun getContentViewLayoutId() = R.layout.activity_login
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/CacheFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.ui.base.BaseFragment
import com.jennifer.andy.simpleeyes.ui.profile.presenter.CachePresenter
import com.jennifer.andy.simpleeyes.ui.profile.view.CacheView
import com.jennifer.andy.simpleeyes.utils.bindView
/**
* Author: andy.xwt
* Date: 2019-09-12 15:51
* Description:我的缓存
*/
class CacheFragment : BaseFragment(), CacheView, View.OnClickListener {
private val mIvBack: ImageView by bindView(R.id.iv_back)
companion object {
@JvmStatic
fun newInstance(): CacheFragment = CacheFragment()
}
override fun initView(savedInstanceState: Bundle?) {
super.initView(savedInstanceState)
mIvBack.setOnClickListener(this)
}
override fun onClick(view: View?) {
when (view?.id) {
R.id.iv_back -> {
pop()
}
else -> {
}
}
//todo 这里打算用WorkManager来做
}
override fun getContentViewLayoutId() = R.layout.fragment_cache
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/ProfileFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.ui.base.BaseFragment
import com.jennifer.andy.simpleeyes.ui.profile.adapter.ProfileSettingAdapter
import com.jennifer.andy.simpleeyes.ui.profile.presenter.ProfilePresenter
import com.jennifer.andy.simpleeyes.ui.profile.view.ProfileView
import com.jennifer.andy.simpleeyes.utils.bindView
/**
* Author: andy.xwt
* Date: 2017/9/22 13:51
* Description:我的
*/
class ProfileFragment : BaseFragment(), ProfileView, View.OnClickListener {
private val mRecycler: RecyclerView by bindView(R.id.rv_profile_recycler)
private val mIvAvatar: SimpleDraweeView by bindView(R.id.iv_avatar)
private val mFlComment: FrameLayout by bindView(R.id.fl_comment_container)
companion object {
@JvmStatic
fun newInstance(): ProfileFragment = ProfileFragment()
private const val MESSAGE = 0 //我的消息
private const val FOCUS = 1 //我的关注
private const val CACHE_POSITION = 2//我的缓存
private const val WATH_HISTORY = 3//观看记录
}
override fun initView(savedInstanceState: Bundle?) {
mIvAvatar.setOnClickListener(this)
mFlComment.setOnClickListener(this)
mRecycler.apply {
val stringArray = resources.getStringArray(R.array.profile_setting)
adapter = ProfileSettingAdapter(arrayListOf(*stringArray))
.apply {
setOnItemClickListener { _, _, position ->
when (position) {
CACHE_POSITION -> {//我的缓存
start(CacheFragment.newInstance())
}
}
}
}
layoutManager = LinearLayoutManager(context)
}
}
override fun onClick(view: View) {
when (view.id) {
R.id.iv_avatar,
R.id.fl_comment_container -> {//跳转到登录界面
ARouter.getInstance().build("/github/Login").navigation()
}
}
}
override fun getContentViewLayoutId() = R.layout.fragment_profile
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/adapter/ProfileSettingAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile.adapter
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.jennifer.andy.simpleeyes.R
/**
* Author: andy.xwt
* Date: 2019/1/15 18:59
* Description:
*/
class ProfileSettingAdapter(data: MutableList?) : BaseQuickAdapter(R.layout.item_profile_setting, data) {
override fun convert(helper: BaseViewHolder, item: String) {
helper.setText(R.id.tv_text, item)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/model/ProfileModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile.model
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
/**
* Author: andy.xwt
* Date: 2017/9/22 13:55
* Description:
*/
class ProfileModel : BaseModel
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/presenter/CachePresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile.presenter
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.profile.view.CacheView
/**
* Author: andy.xwt
* Date: 2019-09-13 16:09
* Description:
*/
class CachePresenter : BasePresenter() {
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/presenter/ProfilePresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile.presenter
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.ui.profile.view.ProfileView
/**
* Author: andy.xwt
* Date: 2017/9/22 13:55
* Description:
*/
class ProfilePresenter : BasePresenter() {
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/view/CacheView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile.view
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2019-09-13 16:08
* Description:
*/
interface CacheView : BaseView {
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/view/ProfileView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.profile.view
import com.jennifer.andy.simpleeyes.ui.base.BaseView
/**
* Author: andy.xwt
* Date: 2017/9/22 13:56
* Description:
*/
interface ProfileView : BaseView {
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/SearchHotActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.search
import android.animation.ValueAnimator
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.view.Gravity
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.google.android.flexbox.FlexDirection
import com.google.android.flexbox.FlexboxLayoutManager
import com.google.android.flexbox.JustifyContent
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.AndyInfo
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.search.adapter.SearchHotAdapter
import com.jennifer.andy.simpleeyes.ui.search.adapter.SearchVideoAdapter
import com.jennifer.andy.simpleeyes.ui.search.presenter.SearchPresenter
import com.jennifer.andy.simpleeyes.ui.search.view.SearchHotView
import com.jennifer.andy.simpleeyes.ui.video.VideoDetailActivity
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.utils.dip2px
import com.jennifer.andy.simpleeyes.utils.showKeyboard
import com.jennifer.andy.simpleeyes.widget.CustomLoadMoreView
import com.jennifer.andy.simpleeyes.widget.SearchHotRemindView
import com.jennifer.andy.simpleeyes.widget.image.CenterAlignImageSpan
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
/**
* Author: andy.xwt
* Date: 2018/4/3 10:54
* Description:搜索界面
*/
class SearchHotActivity : BaseActivity(), SearchHotView {
private val mSearchRemind: SearchHotRemindView by bindView(R.id.rl_search_remind)
private val mSearchView: SearchView by bindView(R.id.searchView)
private val mTvCancel: TextView by bindView(R.id.tv_cancel)
private val multipleStateView: MultipleStateView by bindView(R.id.multiple_state_view)
private val mRecycler: RecyclerView by bindView(R.id.rv_search_recycler)
private lateinit var mHotSearchAdapter: SearchHotAdapter
private lateinit var mSearchVideoAdapter: SearchVideoAdapter
override fun initView(savedInstanceState: Bundle?) {
initSearchView()
mTvCancel.setOnClickListener { finish() }
mPresenter.searchHot()
}
private fun initSearchView() {
mSearchView.isIconified = false
mSearchView.setIconifiedByDefault(false)
//设置输入框提示文字样式
val searchComplete = mSearchView.findViewById(R.id.search_src_text)
searchComplete.gravity = Gravity.CENTER
searchComplete.setHintTextColor(resources.getColor(R.color.gray_66A2A2A2))
searchComplete.textSize = 13f
searchComplete.hint = getDecoratedHint(searchComplete.hint, getDrawable(R.drawable.ic_action_search_no_padding), 50)
//添加搜索监听
mSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
showKeyboard(false)
mPresenter.searchVideoByWord(query)
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
}
/**
* 设置输入框提示文字
*/
private fun getDecoratedHint(hintText: CharSequence, searchHintIcon: Drawable, drawableSize: Int): CharSequence {
searchHintIcon.setBounds(0, 0, drawableSize, drawableSize)
val ssb = SpannableStringBuilder(" ")
ssb.setSpan(CenterAlignImageSpan(searchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
ssb.append(hintText)
return ssb
}
override fun getHotWordSuccess(hotList: MutableList) {
setRecyclerMargin()
startContentAnimation()
mHotSearchAdapter = SearchHotAdapter(hotList)
mHotSearchAdapter.setOnItemClickListener { _, _, position ->
showKeyboard(false)
mPresenter.searchVideoByWord(mHotSearchAdapter.getItem(position)!!)
}
val flexBoxLayoutManager = FlexboxLayoutManager(mContext, FlexDirection.ROW)
flexBoxLayoutManager.justifyContent = JustifyContent.CENTER
mRecycler.layoutManager = flexBoxLayoutManager
mRecycler.adapter = mHotSearchAdapter
}
/**
* 进入内容动画
*/
private fun startContentAnimation() {
val valueAnimator = ValueAnimator.ofInt(multipleStateView.measuredHeight, 0)
valueAnimator.duration = 500
valueAnimator.interpolator = AccelerateInterpolator()
valueAnimator.addUpdateListener {
val value = it.animatedValue as Int
multipleStateView.scrollTo(0, value)
}
valueAnimator.start()
}
override fun showSearchSuccess(queryWord: String, andyInfo: AndyInfo) {
//设置搜索结果
mSearchRemind.setSearchResult(queryWord, andyInfo.total)
//添加了过滤规则,防止搜索的时候崩溃
mSearchVideoAdapter = SearchVideoAdapter(andyInfo.itemList.filter { it.type == SearchVideoAdapter.VIDEO_COLLECTION_WITH_BRIEF || it.type == SearchVideoAdapter.VIDEO })
//跳转到播放界面
mSearchVideoAdapter.onItemClickListener = BaseQuickAdapter.OnItemClickListener { _, _, position ->
val item = mSearchVideoAdapter.getItem(position)
if (item?.type == SearchVideoAdapter.VIDEO) {
VideoDetailActivity.start(mContext, item.data, arrayListOf(), position)
}
}
if (andyInfo.itemList.size > 0) {
resetRecyclerMargin()
} else {
mSearchVideoAdapter.setEmptyView(R.layout.empty_search_word, mRecycler)
}
mSearchVideoAdapter.setOnLoadMoreListener({ mPresenter.loadMoreInfo() }, mRecycler)
mSearchVideoAdapter.setLoadMoreView(CustomLoadMoreView())
mRecycler.layoutManager = LinearLayoutManager(mContext)
mRecycler.adapter = mSearchVideoAdapter
//处理没有更多的情况
if (andyInfo.nextPageUrl == null) {
mSearchVideoAdapter.loadMoreEnd()
}
}
override fun loadMoreSuccess(andyInfo: AndyInfo) {
mSearchVideoAdapter.addData(andyInfo.itemList)
mSearchVideoAdapter.loadMoreComplete()
}
override fun showNoMore() {
mSearchVideoAdapter.loadMoreEnd()
}
override fun showLoading() {
multipleStateView.showLoading()
}
override fun showContent() {
multipleStateView.showContent()
}
override fun showNetError(onClickListener: View.OnClickListener) {
multipleStateView.showNetError(onClickListener)
}
/**
* 重置RecyclerMargin
*/
private fun resetRecyclerMargin() {
val lp = mRecycler.layoutParams as RelativeLayout.LayoutParams
lp.marginEnd = 0
lp.marginStart = 0
mRecycler.layoutParams = lp
}
/**
* 设置RecyclerMargin
*/
private fun setRecyclerMargin() {
val lp = mRecycler.layoutParams as RelativeLayout.LayoutParams
lp.marginEnd = dip2px(30f)
lp.marginStart = dip2px(30f)
mRecycler.layoutParams = lp
}
override fun toggleOverridePendingTransition() = true
override fun getOverridePendingTransition() = TransitionMode.TOP
override fun getContentViewLayoutId() = R.layout.fragment_search_hot
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/adapter/CollectionBriefAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.search.adapter
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.utils.getElapseTimeForShow
/**
* Author: andy.xwt
* Date: 2018/4/9 11:01
* Description:
*/
class CollectionBriefAdapter(data: MutableList) : BaseQuickAdapter(R.layout.item_collection_brief, data) {
override fun convert(helper: BaseViewHolder, item: Content) {
with(helper) {
getView(R.id.iv_image).setImageURI(item.data.cover.feed)
setText(R.id.tv_title, item.data.title)
val description = "#${item.data.category} / ${getElapseTimeForShow(item.data.duration)}"
setText(R.id.tv_desc, description)
}
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/adapter/SearchHotAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.search.adapter
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.jennifer.andy.simpleeyes.R
/**
* Author: andy.xwt
* Date: 2018/4/8 10:16
* Description:
*/
class SearchHotAdapter(data: MutableList) : BaseQuickAdapter(R.layout.item_hot_search, data) {
override fun convert(helper: BaseViewHolder, item: String) {
helper.setText(R.id.tv_text, item)
}
}
================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/adapter/SearchVideoAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.search.adapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseQuickAdapter.OnItemClickListener
import com.chad.library.adapter.base.BaseViewHolder
import com.chad.library.adapter.base.util.MultiTypeDelegate
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.ui.video.VideoDetailActivity
import com.jennifer.andy.simpleeyes.utils.getElapseTimeForShow
import com.jennifer.andy.simpleeyes.widget.ItemHeaderView
import java.util.*
/**
* Author: andy.xwt
* Date: 2018/4/9 10:19
* Description:
*/
class SearchVideoAdapter(data: List?) : BaseQuickAdapter(data) {
/**
* 卡片类型
*/
companion object {
const val VIDEO_COLLECTION_WITH_BRIEF_TYPE = 0
const val VIDEO_TYPE = 1
const val VIDEO_COLLECTION_WITH_BRIEF = "videoCollectionWithBrief"
const val VIDEO = "video"
}
init {
multiTypeDelegate = object : MultiTypeDelegate() {
override fun getItemType(content: Content?): Int {
when (content?.type) {
VIDEO_COLLECTION_WITH_BRIEF -> return VIDEO_COLLECTION_WITH_BRIEF_TYPE
VIDEO -> return VIDEO_TYPE
}
return VIDEO_TYPE
}
}
with(multiTypeDelegate) {
registerItemType(VIDEO_COLLECTION_WITH_BRIEF_TYPE, R.layout.layout_collection_with_brief)
registerItemType(VIDEO_TYPE, R.layout.layout_single_video)
}
}
override fun convert(helper: BaseViewHolder?, content: Content) {
when (helper?.itemViewType) {
VIDEO_COLLECTION_WITH_BRIEF_TYPE -> setCollectionBriefInfo(helper, content)
VIDEO_TYPE -> setSingleVideoInfo(helper, content)
}
}
/**
* 设置带关注人的视频集合信息
*/
private fun setCollectionBriefInfo(helper: BaseViewHolder, content: Content) {
//设置视频集合信息
with(helper) {
getView(R.id.rv_recycler).apply {
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)
adapter = CollectionBriefAdapter(content.data.itemList).apply {
onItemClickListener = OnItemClickListener { _, _, position ->
//跳转到播放视频详情
val item = getItem(position)
VideoDetailActivity.start(mContext, item!!.data, data as ArrayList, position)
}
}
}
//设置头布局
content.data.header?.let { getView(R.id.item_header_view).setHeader(it, content.data.dataType) }
}
}
/**
* 设置单视频信息
*/
private fun setSingleVideoInfo(helper: BaseViewHolder, item: Content) {
val imageView = helper.getView