Browse Source

引入视频播放module,完成作文学习

master
suliang 2 years ago
parent
commit
16204eac4f
47 changed files with 1577 additions and 87 deletions
  1. 2
    0
      .idea/gradle.xml
  2. 11
    0
      .idea/misc.xml
  3. 3
    1
      ProjectErrors.md
  4. 7
    0
      README.md
  5. 1
    0
      app/build.gradle
  6. 5
    1
      app/src/main/AndroidManifest.xml
  7. 8
    1
      app/src/main/java/com/xkl/cdl/adapter/AdapterImageTask.kt
  8. 66
    0
      app/src/main/java/com/xkl/cdl/adapter/AdapterVideo.kt
  9. 109
    0
      app/src/main/java/com/xkl/cdl/data/bean/VideoBean.java
  10. 1
    1
      app/src/main/java/com/xkl/cdl/data/bean/course/CourseDetail.kt
  11. 2
    2
      app/src/main/java/com/xkl/cdl/data/bean/course/Lesson.kt
  12. 7
    5
      app/src/main/java/com/xkl/cdl/data/binding/BindingAdapter.kt
  13. 2
    2
      app/src/main/java/com/xkl/cdl/data/event/LearnEventData.kt
  14. 10
    0
      app/src/main/java/com/xkl/cdl/data/manager/FilePathManager.kt
  15. 74
    15
      app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt
  16. 38
    0
      app/src/main/java/com/xkl/cdl/data/repository/DataRepository.kt
  17. 1
    1
      app/src/main/java/com/xkl/cdl/module/learn/LearnBaseViewModel.kt
  18. 7
    1
      app/src/main/java/com/xkl/cdl/module/learn/LearnCReadingActivity.kt
  19. 7
    4
      app/src/main/java/com/xkl/cdl/module/learn/LearnCReadingViewModel.kt
  20. 2
    2
      app/src/main/java/com/xkl/cdl/module/learn/LearnCTaskActivity.kt
  21. 382
    5
      app/src/main/java/com/xkl/cdl/module/learn/LearnCVideoActivity.kt
  22. 346
    0
      app/src/main/java/com/xkl/cdl/module/learn/LearnCVideoViewModel.kt
  23. 10
    7
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamViewModel.kt
  24. 21
    10
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseLessonFragment.kt
  25. 5
    0
      app/src/main/res/color/video_tab_color.xml
  26. 13
    0
      app/src/main/res/drawable/video_tab_bg.xml
  27. 171
    6
      app/src/main/res/layout/activity_learn_cvideo.xml
  28. 3
    3
      app/src/main/res/layout/include_main_learn_center_course_type_title.xml
  29. 5
    2
      app/src/main/res/layout/item_task_image.xml
  30. 25
    0
      app/src/main/res/layout/item_video_adapter.xml
  31. BIN
      app/src/main/res/mipmap-xxhdpi/sigh_icon.png
  32. 2
    0
      app/src/main/res/values/styles.xml
  33. 6
    0
      app/src/main/res/values/themes.xml
  34. 2
    2
      app/svg/drawable/ic_note.xml
  35. 12
    0
      app/svg/drawable/ic_note_del.xml
  36. 12
    0
      app/svg/drawable/ic_video_blackboard.xml
  37. 12
    0
      app/svg/drawable/ic_video_note.xml
  38. 3
    3
      lib/common/src/main/java/com/suliang/common/base/adapter/BaseRVAdapter.kt
  39. 57
    7
      lib/common/src/main/java/com/suliang/common/util/DateUtil.kt
  40. 15
    3
      lib/common/src/main/java/com/suliang/common/util/DrawableUti.kt
  41. 4
    0
      lib/common/src/main/java/com/suliang/common/util/LogUtil.kt
  42. 3
    3
      lib/common/src/main/java/com/suliang/common/util/thread/AppExecutors.kt
  43. 51
    0
      lib/common/src/main/java/com/suliang/common/widget/MyRadioButton.java
  44. 3
    0
      lib/common/src/main/java/com/suliang/common/widget/TextViewCenterDrawable.java
  45. 40
    0
      lib/common/src/main/java/com/suliang/common/widget/TextViewDrawableTopCenterWithFirst.kt
  46. 1
    0
      settings.gradle
  47. 10
    0
      videoplayer/src/main/res/values/styles.xml

+ 2
- 0
.idea/gradle.xml View File

@@ -14,10 +14,12 @@
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/lib" />
<option value="$PROJECT_DIR$/lib/common" />
<option value="$PROJECT_DIR$/videoplayer" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
<option name="offlineMode" value="true" />
</component>
</project>

+ 11
- 0
.idea/misc.xml View File

@@ -35,17 +35,20 @@
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_24_white.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_8_white.xml" value="0.5010416666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_switch_thumb.xml" value="0.14074074074074075" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_switch_track.xml" value="0.26851851851851855" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/spoken_autoplay_btn_text_color_.xml" value="0.5140625" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_thumb_selector.xml" value="0.3768518518518518" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_track_selector.xml" value="0.3768518518518518" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/test_error_state_button_bg.xml" value="0.45740740740740743" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/video_tab_bg.xml" value="0.26851851851851855" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_exam_learn_spell.xml" value="0.47690217391304346" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_creading.xml" value="0.5" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_ctask.xml" value="0.23353596757852077" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_cvideo.xml" value="0.22407407407407406" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam.xml" value="0.25" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam_word.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_spell.xml" value="0.47690217391304346" />
@@ -91,6 +94,7 @@
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_historical_route.xml" value="0.4859375" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_spell_single_word.xml" value="0.23632218844984804" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_task_image.xml" value="0.23353596757852077" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_video_adapter.xml" value="0.67" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" />
@@ -107,6 +111,8 @@
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_my.xml" value="0.44166666666666665" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_service.xml" value="0.21574074074074073" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_statistics.xml" value="0.44166666666666665" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_note.xml" value="0.3567708333333333" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_note_del.xml" value="0.3567708333333333" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play.xml" value="0.503125" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_pause.xml" value="0.503125" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_stop.xml" value="0.4973958333333333" />
@@ -124,6 +130,11 @@
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/loadding_fragment.xml" value="0.4817708333333333" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/loading_fragment.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/public_title_bar.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_definition_recycler_view.xml" value="0.10277777777777777" />
<entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_item_definition_select_in_player.xml" value="0.10277777777777777" />
<entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_media_controller.xml" value="0.23454913880445796" />
<entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_media_controller_full.xml" value="0.5" />
<entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/video_popup.xml" value="0.67" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.22" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_fullscreen.xml" value="0.10144927536231885" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" />

+ 3
- 1
ProjectErrors.md View File

@@ -53,4 +53,6 @@ DialogFragment原理
BottomSheetDialog 固定高度和原理
BottomSheetDialog中使用TextView滑动的冲突?
Behavior
MMKV实现和保存原理
MMKV实现和保存原理
canvas.translate
视频播放器

+ 7
- 0
README.md View File

@@ -48,6 +48,13 @@ image包: 实现了图片加载的封装
学前总测试 :不让跳过,必须测,用于进行界面判断 和 统计的判断
课时学前测试: 不让跳过,必须测,不然对统计和判断有影响

作文如何记录每个课时是否学习完成:
1 视频 : 1)保存播放的时间点 2)保存有效和总时间 3)保存进度点( 学习完成entityId设置为负值,未学习完成为正值)
2 知识点速记 : 和英语课时一样处理
3 知识点测试:添加一个单词作为进度点, 学习完成有进度,否则没有进度
4 课堂练习: 设置了进度点(为当前显示的entityId)
lesson的learnedIndex 为当前位置,但在学习的时候,需要对当前位置进行-1,第一次学习,初始时,必须为0
学习完成entityId设置为负值,未学习完成为正值




+ 1
- 0
app/build.gradle View File

@@ -72,6 +72,7 @@ dependencies {
implementation fileTree(include: ['*.jar', "*.aar"], dir: 'libs')
// implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation project(path: ':lib:common')
implementation project(path: ':videoplayer')
// implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
// implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
// implementation 'androidx.appcompat:appcompat:1.2.0'

+ 5
- 1
app/src/main/AndroidManifest.xml View File

@@ -20,7 +20,11 @@
android:exported="true" />
<activity
android:name=".module.learn.LearnCVideoActivity"
android:exported="true" />
android:exported="true"
android:theme="@style/Theme.videoTheme"
android:configChanges="orientation|keyboardHidden|screenSize|locale"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden"/>
<activity
android:name=".module.learn.LearnExamActivity"
android:exported="true" />

+ 8
- 1
app/src/main/java/com/xkl/cdl/adapter/AdapterImageTask.kt View File

@@ -6,7 +6,9 @@ import com.suliang.common.base.adapter.BaseAdapterViewHolder
import com.suliang.common.base.adapter.BaseRVAdapterVM
import com.suliang.common.extension.click
import com.suliang.common.util.image.ImageLoader
import com.suliang.common.util.os.ScreenUtil
import com.xkl.cdl.R
import com.xkl.cdl.databinding.ItemTaskImageBinding
import com.xkl.cdl.module.learn.LearnCTaskViewModel
import java.io.File

@@ -22,7 +24,12 @@ class AdapterImageTask(viewModel : LearnCTaskViewModel) : BaseRVAdapterVM<File,
}
override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) {
ImageLoader.loadImage(holder.binding.root as ImageView,getItem(position))
val binding = holder.binding as ItemTaskImageBinding
binding.ivPhoto.layoutParams.height = ScreenUtil.getScreenWidth() / 3 - ScreenUtil.dp2px(36f).toInt()
ImageLoader.loadImage(binding.ivPhoto,getItem(position))
holder.binding.root.click {
if (onItemClickIsInitialized()){
onItemClick.invoke(it,position,getItem(position))

+ 66
- 0
app/src/main/java/com/xkl/cdl/adapter/AdapterVideo.kt View File

@@ -0,0 +1,66 @@
package com.xkl.cdl.adapter

import android.view.ViewGroup
import com.suliang.common.base.adapter.BaseAdapterViewHolder
import com.suliang.common.base.adapter.BaseRVAdapterVM
import com.suliang.common.extension.click
import com.suliang.common.util.DrawableUti
import com.xkl.cdl.R
import com.xkl.cdl.data.bean.VideoBean
import com.xkl.cdl.databinding.ItemVideoAdapterBinding
import com.xkl.cdl.module.learn.LearnCVideoViewModel

/**
* author suliang
* create 2021/4/9 16:44
* Describe: 视频适配器
*/
class AdapterVideo(viewmodel : LearnCVideoViewModel) :
BaseRVAdapterVM<VideoBean.VideoBlackBoard, LearnCVideoViewModel>(viewmodel) {
override fun getItemCount() : Int {
return when (vm.showTab) {
1 -> vm.mCollectVideoBlackBoradList.size //笔记
else -> vm.mVideoBean?.getmVideoBlackBoard()?.size ?: super.getItemCount() //板书
}
}
override fun getItem(position : Int) : VideoBean.VideoBlackBoard {
return when (vm.showTab) {
1 -> vm.mCollectVideoBlackBoradList.get(position) //笔记
else -> vm.mVideoBean?.getmVideoBlackBoard()?.get(position) ?: super.getItem(position) //板书
}
}
override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder {
return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_video_adapter))
}
override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) {
val value = "${position + 1}.${getItem(position).content}"
(holder.binding as ItemVideoAdapterBinding).run {
tvValue.text = value
when (vm.showTab) {
0 -> {
tvValue.setCompoundDrawablesWithIntrinsicBounds(when (getItem(position).collectId) {
0L -> DrawableUti.changeSvgColor(context.resources,
R.drawable.ic_note,
R.color.gray_2) //未收藏
else -> DrawableUti.changeSvgColor(context.resources,
R.drawable.ic_note,
R.color.red_2) //已收藏
}, null, null, null)
}
else -> tvValue.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_note_del, 0, 0, 0) //笔记
}
}
holder.binding.root.click {
if (onItemClickIsInitialized()) {
onItemClick.invoke(it, position, getItem(position))
}
}
}
}

+ 109
- 0
app/src/main/java/com/xkl/cdl/data/bean/VideoBean.java View File

@@ -0,0 +1,109 @@
package com.xkl.cdl.data.bean;

import com.xkl.videoplayer.bean.VideoAnchor;

import java.io.Serializable;
import java.util.List;

/**
* author suliang
* create 2021/3/26 15:43
* Describe: 视频数据表模型
*/
public class VideoBean implements Serializable {
private long videoId;
private String title ;
private String url ; // url为全链接
private long duration ; //总秒数

private List<VideoAnchor> mVideoAnchorList ; //视频锚点
private List<VideoBean.VideoBlackBoard> mVideoBlackBoard ; //视频板书

public long getVideoId() {
return videoId;
}

public void setVideoId(long videoId) {
this.videoId = videoId;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public long getDuration() {
return duration;
}

public void setDuration(long duration) {
this.duration = duration;
}

public List<VideoAnchor> getmVideoAnchorList() {
return mVideoAnchorList;
}

public void setmVideoAnchorList(List<VideoAnchor> mVideoAnchorList) {
this.mVideoAnchorList = mVideoAnchorList;
}

public List<VideoBlackBoard> getmVideoBlackBoard() {
return mVideoBlackBoard;
}

public void setmVideoBlackBoard(List<VideoBlackBoard> mVideoBlackBoard) {
this.mVideoBlackBoard = mVideoBlackBoard;
}

/** 视频板书 */
public static class VideoBlackBoard implements Serializable{
private long videoBlackboardId;
private String content ;
private int sort ;
private long collectId ; //收藏id,是否被收藏

public long getVideoBlackboardId() {
return videoBlackboardId;
}

public void setVideoBlackboardId(long videoBlackboardId) {
this.videoBlackboardId = videoBlackboardId;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public int getSort() {
return sort;
}

public void setSort(int sort) {
this.sort = sort;
}

public long getCollectId() {
return collectId;
}

public void setCollectId(long collectId) {
this.collectId = collectId;
}
}
}

+ 1
- 1
app/src/main/java/com/xkl/cdl/data/bean/course/CourseDetail.kt View File

@@ -17,7 +17,7 @@ class CourseDetail:Serializable{
var after: HashMap<String, Double> = hashMapOf() //章节学后测试成绩,key=>{chapter_id}_{lesson_id} value=>成绩
var right = hashMapOf<String, Int>() //正确条目数,key=>{chapter_id}_{lesson_id} value=>条目数量
var wrong: HashMap<String, Int> = hashMapOf() //错误条目数,key=>{chapter_id}_{lesson_id} value=>条目数量
var lesson_learn_point: HashMap<String, Long> = hashMapOf() //章节学习点 key=>{chapter_id}_{lesson_id} value=>{entity_id)
var lesson_learn_point: HashMap<String, Long> = hashMapOf() //课时学习点 key=>{chapter_id}_{lesson_id} value=>{entity_id)
var exam_w_r_list: HashMap<String, Boolean> = hashMapOf() //课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false
var course_learn_point: String = "" //课程学习进度点 {chapter_id}_{lesson_id}_{entity_id}

+ 2
- 2
app/src/main/java/com/xkl/cdl/data/bean/course/Lesson.kt View File

@@ -27,7 +27,7 @@ data class Lesson(
var lessonPositionInList : Int = 0
/** 该课时的总数据 或者作文关联的数据,如视频关联的内容*/
var wordIds = mutableListOf<Long>()
/** 学习进度位置,为已学下标的位置,在作文课堂练习时,学习取当前条,其他取下一条*/
/** 学习进度位置,为已学下标的位置,在作文课堂练习时,初始值为0开始的下标,学习时已自动-1,取当前下标的值*/
var learnedIndex: Int = -1
/**课时学前测试成绩 */
var beforeTestScore = AppConstants.NOT_DOING
@@ -63,7 +63,7 @@ data class Lesson(

//作文视频使用
/** 视频播放时长 */
var videoPlayTime : Int = 0
var videoPlayTime : Long = 0
/** 视频总时长 */
var videoTotalTime : Int = 0


+ 7
- 5
app/src/main/java/com/xkl/cdl/data/binding/BindingAdapter.kt View File

@@ -51,11 +51,13 @@ object BindingAdapter {
@JvmStatic
fun loadVectorDrawable(view:TextView,svgColor:Int){
view.compoundDrawables.forEachIndexed{ index: Int, drawable: Drawable? ->
when(index){
0 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(DrawableUti.tintDrawable(it,svgColor),null,null,null) }
1 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,DrawableUti.tintDrawable(it,svgColor),null,null) }
2 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,null,DrawableUti.tintDrawable(it,svgColor),null) }
3 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,null,null,DrawableUti.tintDrawable(it,svgColor),) }
drawable?.let {
when(index){
0 -> view.setCompoundDrawablesWithIntrinsicBounds(DrawableUti.tintDrawable(it,svgColor),null,null,null)
1 -> view.setCompoundDrawablesWithIntrinsicBounds(null,DrawableUti.tintDrawable(it,svgColor),null,null)
2 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,DrawableUti.tintDrawable(it,svgColor),null)
3 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,null,DrawableUti.tintDrawable(it,svgColor))
}
}
}
}

+ 2
- 2
app/src/main/java/com/xkl/cdl/data/event/LearnEventData.kt View File

@@ -20,8 +20,8 @@ class LearnEventData(val subjectId : Int, var courseId : Long, val actionFlag :
//学习结束 newErrorMap 保存为所有的错误,包含学前和课程前的测试,主要用与后面进行小游戏的数据加载
//视频播放结束,数据传递的播放时间点
var videoPlayTime : String = ""
//视频播放结束,数据传递的播放时间点, 不需要,直接更新课时后,可从课时中获取
// var videoPlayTime : String = ""
//作文使用,发送消息时,是否只更新进度点,而不需要重算进度,进行上传进度
var isOnlyUpdatePoint = false

+ 10
- 0
app/src/main/java/com/xkl/cdl/data/manager/FilePathManager.kt View File

@@ -39,6 +39,12 @@ class FilePathManager {
fun getMp4PngRootPath():String{
return FileUtil.getSaveDirPath("mp4png")
}
/**作文视频瞄点png保存的文件*/
fun getMp4PngFile(subjectId : Int,coursePackId : Long,courseId : Long, pngName:String):File{
return File(getMp4PngRootPath(),"$subjectId/$coursePackId/$courseId/$pngName")
}
/**作文课外练习png保存的文件夹*/
fun getPngRootPath():String{
return FileUtil.getSaveDirPath("png")
@@ -52,6 +58,10 @@ class FilePathManager {
fun getMp4RootPath():String{
return FileUtil.getSaveDirPath("mp4")
}
/**作文课外练习png保存的文件*/
fun getMp4File(subjectId : Int,coursePackId : Long,courseId : Long, fileName:String):File{
return File(getMp4RootPath(), "$subjectId/$coursePackId/$courseId/$fileName")
}

}


+ 74
- 15
app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt View File

@@ -6,16 +6,19 @@ import androidx.core.database.getBlobOrNull
import com.google.common.base.Joiner
import com.suliang.common.AppConfig
import com.suliang.common.util.AppGlobals
import com.suliang.common.util.DateUtil
import com.suliang.common.util.file.FileUtil
import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.CompositionReadingBean
import com.xkl.cdl.data.bean.CompositionTaskBean
import com.xkl.cdl.data.bean.LearnWord
import com.xkl.cdl.data.bean.VideoBean
import com.xkl.cdl.data.bean.course.CourseDetail
import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.manager.FilePathManager
import com.xkl.videoplayer.bean.VideoAnchor
import io.reactivex.rxjava3.annotations.NonNull
import net.sqlcipher.database.SQLiteDatabase
import java.io.File
@@ -104,9 +107,6 @@ object DBCourseManager {
val learnIndex = wordIds.indexOf(detail.lesson_learn_point.getOrElse(key, { -1 })) //学习位置,当前位置为已学
// 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 todo 分课程类型设置进度点
val learnIsOver = wordIds.size - 1 == learnIndex
val lesson = Lesson(base.subjectId, base.coursePackId, base.coursePackType, base.courseId, base.courseType,
chapterId, chapterName, lessonId, lessonName).apply {
lessonPositionInList = positionIndex
@@ -117,16 +117,31 @@ object DBCourseManager {
beforeTestScore = detail.before.getOrElse(key, { AppConstants.NOT_DOING }) //课时学前测试成绩
afterTestScore = detail.after.getOrElse(key, { AppConstants.NOT_DOING }) //课时学后测试成绩
this.learnedIndex = learnIndex //学习位置,当前位置为已学
this.learnIsOver = learnIsOver
this.lessonType = lessonType
}
// 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 todo 分课程类型设置进度点 口语未做判断处理
lesson.learnIsOver = when(base.coursePackType){
//作文课时,需要根据课时属性,判断本课时是否学习完成
AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> when(lesson.lessonType){
AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> if (learnIndex == -1) false else learnIndex < 0
AppConstants.LESSON_TYPE_COMPOSITION_KNOWLEDGE -> wordIds.size - 1 == learnIndex
AppConstants.LESSON_TYPE_COMPOSITION_EXAM -> learnIndex != -1
AppConstants.LESSON_TYPE_COMPOSITION_READING -> if (learnIndex == -1) {
lesson.learnedIndex = 0 //设其初始值为0
false
} else learnIndex < 0
else -> false
}
else -> wordIds.size - 1 == learnIndex
}
mutableList.add(lesson)
positionIndex += 1
// TODO: 2022/5/9 对课时数量进行限制,课时太多,开发人员不好进行测试
val needBreak = when (base.coursePackType) {
AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> positionIndex == 11 //保留十个课时
AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN -> positionIndex == 7 //保留六个课时
else -> positionIndex == 3 //保留三个课时
AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> positionIndex == 6 //保留十个课时
AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN -> positionIndex == 4 //保留六个课时
else -> positionIndex == 1 //保留三个课时
}
if (needBreak) {
break
@@ -638,18 +653,62 @@ object DBCourseManager {
getString(8),
position))
position++
result.add(CompositionTaskBean(getLong(2),
getLong(4),
getLong(3),
getString(6),
getString(7),
getString(8),
position))
position++
}
close()
}
return result
}
/***
* 查询视频数据
* @param dbBaseControl DbControlBase
* @param lesson Lesson
*/
fun queryVideoData(dbBaseControl : DbControlBase, lesson : Lesson) : VideoBean {
val result = VideoBean()
open(dbBaseControl)
var sql = "SELECT * FROM video WHERE video_id = ${lesson.wordIds[0]}"
mDataBase?.rawQuery(sql,null)?.run {
while (moveToNext()){
result.apply {
videoId = getLong(2)
title = getString(3)
url = getString(4).substringAfterLast("/")
duration = getLong(5)
}
}
close()
}
//关键点数据
val videoAnchorList = mutableListOf<VideoAnchor>()
sql = "SELECT * FROM video_anchor WHERE video_id = ${lesson.wordIds[0]}"
mDataBase?.rawQuery(sql,null)?.run {
while (moveToNext()){
val videoAnchor = VideoAnchor().apply {
anchor_id = getLong(3)
title = getString(5)
time = DateUtil.getTimeSecond(getString(2))
}
videoAnchorList.add(videoAnchor)
}
close()
}
result.setmVideoAnchorList(videoAnchorList)
//查询板书信息
val videoBlackBoardList = mutableListOf<VideoBean.VideoBlackBoard>()
sql = "SELECT * FROM video_blackboard WHERE video_id = ${lesson.wordIds[0]} ORDER by sort"
mDataBase?.rawQuery(sql,null)?.run {
while (moveToNext()){
videoBlackBoardList.add( VideoBean.VideoBlackBoard().apply {
videoBlackboardId = getLong(2)
content = getString(4)
sort = getInt(5)
})
}
close()
}
result.setmVideoBlackBoard(videoBlackBoardList)
return result
}
}

+ 38
- 0
app/src/main/java/com/xkl/cdl/data/repository/DataRepository.kt View File

@@ -1,6 +1,8 @@
package com.xkl.cdl.data.repository

import com.googlecode.protobuf.format.JsonFormat
import com.suliang.common.util.file.FileUtil
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.course.CourseDetail
import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.manager.db.DBCourseManager
@@ -8,6 +10,7 @@ import com.xkl.cdl.data.manager.db.DbControlBase
import io.reactivex.rxjava3.core.Observable
import mqComsumerV1.Struct
import java.io.File
import java.io.FileOutputStream

/**
* author suliang
@@ -94,6 +97,41 @@ object DataRepository {
calcCourseVocabularyAndEfficiency(subjectId,coursePackId,courseId)
}
// TODO: 2022/4/29 调用保存方法

var fileName = ""
//写入文件
if (record.examCount > 0 ){ //当前为测试
val s = when (record.getExam(0).type.toInt()) { //测试类型
AppConstants.TEST_TYPE_BEFORE_TOTAL -> "学前总测试"
AppConstants.TEST_TYPE_BEFORE -> "章节学前测试"
AppConstants.TEST_TYPE_AFTER -> "章节学后测试"
AppConstants.TEST_TYPE_AFTER_TOTAL -> "学前总测试"
AppConstants.TEST_TYPE_COMPOSITION -> "作文知识点测试"
else -> ""
}
fileName = "${record.getExam(0).projectId}_${record.getExam(0).packId}_${record.getExam(0).courseId}_${record.getExam(0).chapterId}_${record.getExam(0).lessonId}_$s"
}else if (record.entityCount > 0 ){ //没有测试,则为学习
fileName = "${record.getEntity(0).projectId}_${record.getEntity(0).packId}_${record.getEntity(0).courseId}_${record.getEntity(0).chapterId}_${record.getEntity(0).lessonId}_课时学习"
}else if (record.scheduleCount > 0 ) { //进度更新
fileName = "${record.getSchedule(0).projectId}_${record.getSchedule(0).packId}_${record.getSchedule(0).courseId}_进度更新"
}else { // 时长更新
fileName = "${record.getDuration(0).projectId}_${record.getDuration(0).packId}_${record.getDuration(0).courseId}_时长更新"
}
FileUtil.getSaveDirFile("saveData").let {
if (!it.exists()){
it.mkdirs()
}
//序列化输出
val string = JsonFormat.printToString(record.build())
val saveArray = record.build().toByteArray()
val saveStringName = "${fileName}_jsonString.txt"
FileUtil.writeBytesToFile(it,saveStringName,string.toByteArray())
val saveByteArrayName = "${fileName}_byteArray.txt"
FileUtil.writeBytesToFile(it,saveByteArrayName,saveArray)
}
return true
}
/**

+ 1
- 1
app/src/main/java/com/xkl/cdl/module/learn/LearnBaseViewModel.kt View File

@@ -63,7 +63,7 @@ abstract class LearnBaseViewModel : BaseViewModel() {
/** 停止计时 */
fun stopTotalCountTing() {
// LogUtil.e("停止总计时")
LogUtil.e("停止总计时")
timeTask?.cancel()
timeTask = null
}

+ 7
- 1
app/src/main/java/com/xkl/cdl/module/learn/LearnCReadingActivity.kt View File

@@ -229,7 +229,13 @@ class LearnCReadingActivity : BaseActivityVM<ActivityLearnCreadingBinding, Learn
dialog.dismissAllowingStateLoss()
when {
isRightClick -> vm.showOrDismissBackDialogForTime(false)
else -> vm.saveData()
else ->{
if (!vm.isLearned){ //没有学习直接退出
finish()
}else {
vm.saveData()
}
}
}
}
}.show(supportFragmentManager, "learn_back_dialog")

+ 7
- 4
app/src/main/java/com/xkl/cdl/module/learn/LearnCReadingViewModel.kt View File

@@ -20,10 +20,13 @@ class LearnCReadingViewModel : LearnBaseViewModel() {
/* 操作数据 */
val learnData = DataTransferHolder.instance.getData<LearnData>()
//当前学习下标
var currentLearnIndex = learnData.lesson.learnedIndex
//当前学习下标 初始为0 ,非-1
var currentLearnIndex = learnData.lesson.learnedIndex - 1
//记录最后一条的item是否进行了学习, 初始标记直接为课时学习是否完成的标记就好了
var lastIsLearned = learnData.lesson.learnIsOver
//当前是否学习
var isLearned = false
//当前学习Bean
val currentValue = MutableLiveData<CompositionReadingBean>()
@@ -145,7 +148,7 @@ class LearnCReadingViewModel : LearnBaseViewModel() {
courseId = it.courseId
chapterId = it.chapterId
lessonId = it.lessonId
entityId = currentValue.value!!.id
entityId = if (learnData.lesson.learnIsOver || lastIsLearned) -currentValue.value!!.id else currentValue.value!!.id
}
isCompExercise = true
tag = "android"
@@ -169,7 +172,7 @@ class LearnCReadingViewModel : LearnBaseViewModel() {
emitter.onNext(DataRepository.saveRecord(record))
emitter.onComplete()
}.compose(diskIo2Main()).subscribe {
learnData.lesson.learnedIndex = currentLearnIndex - 1
learnData.lesson.learnedIndex = currentLearnIndex
val temp = learnData.lesson.learnIsOver == lastIsLearned
//发送信息,更改进度点
if (!temp){

+ 2
- 2
app/src/main/java/com/xkl/cdl/module/learn/LearnCTaskActivity.kt View File

@@ -1,6 +1,5 @@
package com.xkl.cdl.module.learn

import android.media.Image
import android.os.Bundle
import android.text.TextUtils
import android.widget.ImageView
@@ -18,7 +17,6 @@ import com.suliang.common.extension.click
import com.xkl.cdl.R
import com.xkl.cdl.adapter.AdapterBottomDialogSwitch
import com.xkl.cdl.adapter.AdapterImageTask
import com.xkl.cdl.data.bean.CompositionReadingBean
import com.xkl.cdl.data.bean.CompositionTaskBean
import com.xkl.cdl.data.manager.FilePathManager
import com.xkl.cdl.databinding.ActivityLearnCtaskBinding
@@ -58,6 +56,7 @@ class LearnCTaskActivity : BaseActivityVM<ActivityLearnCtaskBinding,LearnCTaskVi
popupView.updateSrcView(binding.recyclerView.getChildAt(position) as ImageView)
}
}, SmartGlideImageLoader())
.isShowSaveButton(false)
.show()
}
}
@@ -78,6 +77,7 @@ class LearnCTaskActivity : BaseActivityVM<ActivityLearnCtaskBinding,LearnCTaskVi
val fileList = split(",").map {
FilePathManager.getPngFile(vm.lesson.subjectId,vm.lesson.coursePackId,vm.lesson.courseId,it.substringAfterLast ("/"))
}.toMutableList()
(binding.recyclerView.adapter as AdapterImageTask).setData(fileList)
}
}

+ 382
- 5
app/src/main/java/com/xkl/cdl/module/learn/LearnCVideoActivity.kt View File

@@ -1,12 +1,389 @@
package com.xkl.cdl.module.learn

import androidx.appcompat.app.AppCompatActivity
import android.content.pm.ActivityInfo
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.view.WindowInsets
import android.view.WindowManager
import android.widget.RadioGroup
import androidx.core.view.ViewCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.suliang.common.base.activity.BaseActivityVM
import com.suliang.common.extension.click
import com.suliang.common.util.LogUtil
import com.suliang.common.util.image.GlideApp
import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.R
import com.xkl.cdl.adapter.AdapterVideo
import com.xkl.cdl.data.bean.VideoBean
import com.xkl.cdl.data.manager.FilePathManager
import com.xkl.cdl.databinding.ActivityLearnCvideoBinding
import com.xkl.cdl.dialog.CommonDialog
import com.xkl.cdl.dialog.CommonDialogBean
import com.xkl.videoplayer.bean.PineMediaPlayerBean
import com.xkl.videoplayer.bean.PineMediaUriSource
import com.xkl.videoplayer.component.PineMediaPlayerComponent
import com.xkl.videoplayer.component.PineMediaWidget
import com.xkl.videoplayer.widget.PineMediaController
import com.xkl.videoplayer.widget.adapter.DefaultVideoControllerAdapter
import com.zackratos.ultimatebarx.ultimatebarx.statusBar
import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly
import java.io.File
import java.util.*

class LearnCVideoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_learn_cvideo)
class LearnCVideoActivity : BaseActivityVM<ActivityLearnCvideoBinding, LearnCVideoViewModel>() {
//播放器控件绑定的播放器接口
private var mPlayer : PineMediaWidget.IPineMediaPlayer? = null
private val defaultVideoControllerAdapter : DefaultVideoControllerAdapter by lazy {
DefaultVideoControllerAdapter(this)
}
//播放器控制器控件
private val mController : PineMediaController by lazy {
PineMediaController(this)
}
override fun initViewModel() : LearnCVideoViewModel {
return ViewModelProvider(this)[LearnCVideoViewModel::class.java]
}
override fun initActivity(savedInstanceState : Bundle?) {
//初始化播放器控件
initVideoControl()
//设置板书数据并跟新
initTabRecyclerView()
//初始化事件
initEvent()
}
override fun initStatusBar() {
statusBarOnly {
fitWindow = true
color = Color.WHITE
light = true
}
}
override fun loadData() {
vm.queryVideoData().observe(this) {
//设置瞄点图片所在文件
val anchorPngFile = FilePathManager.getMp4PngFile(vm.lesson.subjectId, vm.lesson.coursePackId, vm.lesson.courseId,
vm.mVideoBean!!.url + ".png")
if (anchorPngFile.exists()) {
mController.setSpritFile(anchorPngFile.path)
}
//初始化视频
initVideoMovie()
//有数据了,更新recyclerView显示,数据为适配器直接从vm中获取的
binding.recyclerview.adapter?.notifyDataSetChanged()
}
vm.uploadDate.observe(this){
finish()
}
}
/** 初始化播放器 */
private fun initVideoControl() {
defaultVideoControllerAdapter.setActionListener(controllerListener)
mController.setMediaControllerAdapter(defaultVideoControllerAdapter)
mController.setFirstPlayOver(vm.lesson.learnIsOver) //设置第一次是否播放完成
mController.setMaxPlayTime(vm.currentPlayTime.toInt()) //设置播放的最大时间
LogUtil.e("lesson current play time = " + vm.currentPlayTime)
//为videoView设置获取时间监听器
binding.videoView.setServiceDateListener { System.currentTimeMillis() }
binding.videoView.init("学考乐", mController, true, true)
binding.videoView.setKeepScreenOn(true)
binding.videoView.disableBackPressTip()
mPlayer = binding.videoView.getMediaPlayer()
mPlayer?.setAutocephalyPlayMode(false, true) //设置是否为独立播放模式(是否与播放界面共生命周期)
}
private fun initTabRecyclerView() {
binding.recyclerview.apply {
layoutManager = LinearLayoutManager(this@LearnCVideoActivity, LinearLayoutManager.VERTICAL, false)
adapter = AdapterVideo(vm).apply {
onItemClick = { v, position, item ->
// TODO: 2022/5/25 点击事件,处理收藏和取消收藏
// when(item.collectId){
// 0L -> vm.addCollect(item).observe(this@LearnCVideoActivity){
// notifyDataSetChanged()
// }
// else -> vm.removeCollect(item).observe(this@LearnCVideoActivity){
// notifyDataSetChanged()
// }
// }
}
}
}
}
private fun initEvent() {
//tab切换监听
binding.radioGroup.setOnCheckedChangeListener(RadioGroup.OnCheckedChangeListener { group, checkedId ->
if (checkedId == R.id.rb_video_blackboard) {
vm.showTab = 0
} else {
vm.showTab = 1
}
binding.recyclerview.adapter?.notifyDataSetChanged()
})
//竖屏重新加载视频
binding.reloadBtn.click { v ->
binding.stopView.setVisibility(View.GONE)
vm.isStop.setValue(false)
mPlayer?.reLoad()
}
//横屏重新加载视频
binding.reloadBtnFull.click { v ->
binding.stopViewFull.setVisibility(View.GONE)
vm.isStop.setValue(false)
mPlayer?.reLoad()
}
//加载失败:竖屏返回退出
binding.stopBack.click { v ->
onBackPressed()
}
//加载失败:横屏返回退出
binding.stopBackFull.setOnClickListener { v ->
fullScreenSwitch(false)
initStatusBar()
binding.videoView.toggleFullScreenMode(true)
}
}
/** 初始化播放视频 */
private fun initVideoMovie() {
vm.mVideoBean?.let { vb ->
getMediaUriSourceList(vb.url)?.let { videoPath : PineMediaUriSource ->
vm.pineMediaPlayerBean = PineMediaPlayerBean("0", vb.title, videoPath, PineMediaPlayerBean.MEDIA_TYPE_VIDEO, null,
null, null).apply {
currentDefinition = PineMediaUriSource.MEDIA_DEFINITION_VHD //设置清晰度
videoAnchorList = vb.getmVideoAnchorList() //设置视频瞄点内容
duration = vb.duration //总时间
}
mPlayer?.let { mp ->
mp.setPlayingMedia(vm.pineMediaPlayerBean) //设置播放参数
//设置播放监听
mp.setCompletionListener {
vm.currentPlayTime = 0
vm.isCompletion = true
//播放完成 :设置第一次播放完成
binding.videoView.mediaController.setFirstPlayOver(true)
}
mp.setOnStopListener {
when {
!vm.isCompletion -> { //非播完后暂停状态
vm.isStop.value = true
when (requestedOrientation) {
//横屏
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE -> binding.stopViewFull.visibility = View.VISIBLE
else -> binding.stopView.visibility = View.VISIBLE
}
}
else -> {
vm.isStop.value = false
mp.reLoad()
}
}
}
//播放中监听
mp.setPlayingListener { millSeconds : Int, duration : Int ->
vm.isLearn = true
vm.currentPlayTime = millSeconds.toLong()
binding.videoView.mediaController.setMaxPlayTime(millSeconds) //设置播放的最大值
//98%则算本课时学习完成
if (!vm.isCompletion && millSeconds.toDouble() / duration >= 0.98) {
vm.isCompletion = true
binding.videoView.mediaController.setFirstPlayOver(true)
}
}
//设置缓存位置、播放位置
LogUtil.e("开始播放设置播放位置时间:${vm.currentPlayTime}")
mp.setCachPosition(vm.currentPlayTime.toInt())
}
}
}

// //播放不流畅监听
// mPlayer.setPlayNotFluencyListener(object : PineMediaPlayerComponent.OnPlayNotFluencyListener() {
// fun onPlayNotFluency() {
// AppExecutors.getInstance().mainThread().execute {
// var currentDefinition : Int = vm.pineMediaPlayerBean.getCurrentDefinition()
// if (currentDefinition != PineMediaUriSource.MEDIA_DEFINITION_SD) {
// val oldPosition : Int = vm.pineMediaPlayerBean.getCurrentDefinitionPosition()
// currentDefinition = if (currentDefinition == PineMediaUriSource.MEDIA_DEFINITION_VHD) {
// PineMediaUriSource.MEDIA_DEFINITION_HD
// } else {
// PineMediaUriSource.MEDIA_DEFINITION_SD
// }
// val newPosition : Int = vm.pineMediaPlayerBean.getCurrentDefinitionPosition()
// vm.pineMediaPlayerBean.setCurrentDefinition(currentDefinition)
// mPlayer.savePlayMediaState()
// defaultVideoControllerAdapter.videoDefinitionSelected(vm.pineMediaPlayerBean, oldPosition,
// newPosition)
// }
// }
// }
// })
//设置缓存位置、播放位置
// LogUtil.e(TAG, "设置播放位置: " + vm.currentPlayTime)
// mPlayer.setCachPosition(vm.currentPlayTime as Int * 1000)
}
fun getMediaUriSourceList(mp4fileName : String) : PineMediaUriSource? {
val mp4 = FilePathManager.getMp4File(vm.lesson.subjectId, vm.lesson.coursePackId, vm.lesson.courseId, mp4fileName)
if (mp4.exists()) {
return PineMediaUriSource(Uri.parse(mp4.path), PineMediaUriSource.MEDIA_DEFINITION_VHD)
}
return null
}


// fun getMediaUriSourceList(basePath : String) : ArrayList<PineMediaUriSource> {
// val pinePlayerUriSourceList : ArrayList<PineMediaUriSource> = ArrayList<PineMediaUriSource>()
// var pineMediaUriSource : PineMediaUriSource
// pineMediaUriSource = PineMediaUriSource(Uri.parse("$basePath.mp4"), PineMediaUriSource.MEDIA_DEFINITION_VHD)
// pinePlayerUriSourceList.add(pineMediaUriSource)
// pineMediaUriSource = PineMediaUriSource(Uri.parse(basePath + "_720.mp4"), PineMediaUriSource.MEDIA_DEFINITION_HD)
// pinePlayerUriSourceList.add(pineMediaUriSource)
// pineMediaUriSource = PineMediaUriSource(Uri.parse(basePath + "_480.mp4"), PineMediaUriSource.MEDIA_DEFINITION_SD)
// pinePlayerUriSourceList.add(pineMediaUriSource)
// // pineMediaUriSource = new PineMediaUriSource(
//// Uri.parse(basePath + ".mp4"),
//// PineMediaUriSource.MEDIA_DEFINITION_VHD);
//// pinePlayerUriSourceList.add(pineMediaUriSource);
////
//// pineMediaUriSource = new PineMediaUriSource(
//// Uri.parse("https://cdn.xuekaole.com/videos/cn_zw/test/ea81f0e8f3651adad34fe7add5a3d26d_720.mp4"),
//// PineMediaUriSource.MEDIA_DEFINITION_HD);
//// pinePlayerUriSourceList.add(pineMediaUriSource);
////
//// pineMediaUriSource = new PineMediaUriSource(
//// Uri.parse("https://cdn.xuekaole.com/videos/cn_zw/test/ea81f0e8f3651adad34fe7add5a3d26d_480.mp4"),
//// PineMediaUriSource.MEDIA_DEFINITION_SD);
//// pinePlayerUriSourceList.add(pineMediaUriSource);
// return pinePlayerUriSourceList
// }
//控制器事件监听器
private val controllerListener : PineMediaController.ControllersActionListener = object :
PineMediaController.ControllersActionListener() {
//小屏退出
override fun onBackBtnClick(goBackBtn : View,
player : PineMediaWidget.IPineMediaPlayer,
isFullScreenMode : Boolean) : Boolean {
onBackPressed()
return false
}
/** 全屏退出 */
override fun onGoBackBtnClick(goBackBtn : View,
player : PineMediaWidget.IPineMediaPlayer,
isFullScreenMode : Boolean) : Boolean {
fullScreenSwitch(false)
return false
}
/** 屏幕切换 */
override fun onFullScreenBtnClick(fullScreenBtn : View, player : PineMediaWidget.IPineMediaPlayer) : Boolean {
if (requestedOrientation != ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { //竖屏切换横屏
fullScreenSwitch(true)
} else { //切换竖屏
fullScreenSwitch(false)
}
return false
}
}
private fun fullScreenSwitch(isFullScreen : Boolean) {
if (isFullScreen) {
//全屏 隐藏状态栏
hideSystemUI()
if (vm.isStop.value!!) {
binding.stopView.setVisibility(View.GONE)
binding.stopViewFull.setVisibility(View.VISIBLE)
}
} else {
//退出全屏
showSystemUI()
if (vm.isStop.value!!) {
binding.stopView.setVisibility(View.VISIBLE)
binding.stopViewFull.setVisibility(View.GONE)
}
}
}
private fun hideSystemUI() {
statusBarOnly {
fitWindow = false
}
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN)
}
private fun showSystemUI() {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
initStatusBar()
}
override fun onRestart() {
super.onRestart()
//如果是横屏
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
fullScreenSwitch(true)
}
}
override fun onStop() {
mPlayer?.stop()
super.onStop()
}
override fun onDestroy() {
mPlayer?.release()
super.onDestroy()
}
override fun onBackPressed() {
//如果没有学习,直接关闭
if (!vm.isLearn){
finish()
return
}
vm.showOrDismissBackDialogForTime(true)
CommonDialog.newInstance(
CommonDialogBean(titleText = R.string.quit_learn_title, contentText = R.string.quit_learn_content, leftText = R.string.quit, rightText = R.string.cancel)).apply {
onCommonDialogButtonClickListener = { dialog, isRightClick ->
dialog.dismissAllowingStateLoss()
when {
isRightClick -> {
vm.showOrDismissBackDialogForTime(false)
mController.doPauseResume()
}
else -> vm.saveData()
}
}
}.show(supportFragmentManager, "learn_back_dialog")
}
}

+ 346
- 0
app/src/main/java/com/xkl/cdl/module/learn/LearnCVideoViewModel.kt View File

@@ -0,0 +1,346 @@
package com.xkl.cdl.module.learn

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.jeremyliao.liveeventbus.LiveEventBus
import com.suliang.common.extension.diskIo2Main
import com.suliang.common.util.DateUtil
import com.suliang.common.util.LogUtil
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.DataTransferHolder
import com.xkl.cdl.data.bean.VideoBean
import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.event.LearnEventData
import com.xkl.cdl.data.manager.db.DBCourseManager
import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.data.repository.DataRepository
import com.xkl.videoplayer.bean.PineMediaPlayerBean
import io.reactivex.rxjava3.core.Observable
import mqComsumerV1.Struct


class LearnCVideoViewModel : LearnBaseViewModel() {
val lesson = DataTransferHolder.instance.getData<Lesson>() //视频课时lesson
var currentPlayTime : Long = lesson.videoPlayTime //当前播放时间: 毫秒
private val dbBaseControl : DbControlBase = DbControlBase(lesson.subjectId, lesson.coursePackId, lesson.coursePackType, lesson.courseId, lesson.courseType)
var isCompletion = false //是否播放完成
var isLearn = false //是否有播放记录
var mVideoBean : VideoBean? = null //视频数据
//选中tab 0:板书 1:笔记
var showTab = 0
private var timeForLong : Long = 0 //本次学习开始的时间
private var clientTag : String? = null
//视频源
var pineMediaPlayerBean : PineMediaPlayerBean? = null
var isStop = MutableLiveData(false)
//我的笔记
var mCollectVideoBlackBoradList : MutableList<VideoBean.VideoBlackBoard> = ArrayList<VideoBean.VideoBlackBoard>()
//上传数据监听
val uploadDate = MutableLiveData<Boolean>()
init {
clientTag = "android"
timeForLong = System.currentTimeMillis()
isRunValidTime = false
}
/** 获取视频数据 */
fun queryVideoData() : MutableLiveData<Boolean> {
val result = MutableLiveData<Boolean>()
// TODO: 2022/5/24 获取该课时下的收藏信息 板书设置收藏id,同时对笔记进行排序
// io.reactivex.rxjava3.core.Observable.create<AppApi.CompCollectionDetailedResponse> {
// val build = CompCollectionDetailedRequest.newBuilder().apply {
// userId = 1
// productId = lesson.courseId
// typeId = 2
// columnId = lesson.chapterId
// }.build()
// }
io.reactivex.rxjava3.core.Observable.create<Boolean> {
mVideoBean = DBCourseManager.queryVideoData(dbBaseControl,lesson)
it.onNext(true)
it.onComplete()
}.compose(diskIo2Main())
.subscribe{
result.value = true
}
return result
}
/* *//** 取消笔记 *//*
fun removeCollect(item : VideoBean.VideoBlackBoard) : MutableLiveData<Boolean>? {
val result = MutableLiveData<Boolean>()
//体验账号,当前页假数据
if (!UserInfoManager.getInstance().isOfficialAccount()) {
item.setCollectId(0)
mCollectVideoBlackBoradList.remove(item)
result.postValue(true)
return result
}
DataRepository.getInstance()
.removeCollect(item.getCollectId())
.subscribe(object : BaseObserver<AppApi.CompRemoveCollectResponse?>(this) {
fun onSuccess(data : AppApi.CompRemoveCollectResponse?) {
item.setCollectId(0)
mCollectVideoBlackBoradList.remove(item)
result.postValue(true)
}
fun onFailed(code : Int, msg : String?) {
getToast().setValue(XKLApplication.getInstance().getString(R.string.remove_collect_error))
}
})
return result
}
*//** 添加笔记 *//*
fun addCollect(item : VideoBean.VideoBlackBoard) : MutableLiveData<Boolean>? {
val result = MutableLiveData<Boolean>()
//体验账号,当前页假数据
if (!UserInfoManager.getInstance().isOfficialAccount()) {
item.setCollectId(mCollectVideoBlackBoradList.size + 1)
mCollectVideoBlackBoradList.add(item)
result.postValue(true)
return result
}
DataRepository.getInstance()
.collect(getLesson().getCourseId(), getLesson().getChapterId(), item.getVideoBlackboardId(),
SyncStateContract.Constants.COMPOSITON_COLLECT_VIDEO_BLACK_BOOK)
.subscribe(object : BaseObserver<AppApi.CompCollectResponse?>(this) {
fun onSuccess(data : AppApi.CompCollectResponse) {
val collectedMap = data.collectedMap
if (collectedMap != null) {
val aLong = collectedMap[item.getVideoBlackboardId()]
if (aLong != null) {
item.setCollectId(aLong)
mCollectVideoBlackBoradList.add(item)
Collections.sort(mCollectVideoBlackBoradList,
Comparator<Any?> { o1, o2 -> o1.getSort() - o2.getSort() })
}
}
result.postValue(true)
}
fun onFailed(code : Int, msg : String?) {
getToast().setValue(XKLApplication.getInstance().getString(R.string.add_collect_error))
}
})
return result
}
*/
//是否显示返回弹窗
private var isShowBackDialog = false
override fun onResume(owner : LifecycleOwner) {
super.onResume(owner)
if (!isShowBackDialog){
startTotalCounting()
}
}
override fun onPause(owner : LifecycleOwner) {
super.onPause(owner)
if (!isShowBackDialog){
stopTotalCountTing()
}
}
/**
* 显示或关闭的返回弹窗, 显示时关闭总计时 关闭时,开启总计时
* @param isShow Boolean
*/
fun showOrDismissBackDialogForTime(isShow : Boolean) {
isShowBackDialog = isShow
when{
isShowBackDialog -> stopTotalCountTing()
else -> startTotalCounting()
}
}
fun saveData() {
if (!isLearn) { //没有播放视频
uploadDate.value = true
return
}
// TODO: 2022/5/26 保存视频的播放时间
Observable.create<Boolean> { emitter ->
val record = Struct.Record.newBuilder().apply {
//进度点
addEntity(Struct.LearnEntity.newBuilder().apply {
lesson.let {
projectId = it.subjectId.toLong()
packId = it.coursePackId
courseId = it.courseId
chapterId = it.chapterId
lessonId = it.lessonId
entityId = if (it.learnIsOver || isCompletion) -it.wordIds[0] else it.wordIds[0] // 学习完成entityId设置为负值,未学习完成为正值
}
tag = "android"
created = getCurrentDateWithString()
})
//时间
addDuration(Struct.LearnDuration.newBuilder().apply {
lesson.let {
projectId = it.subjectId.toLong()
packId = it.coursePackId
courseId = it.courseId
categoryId = it.coursePackType.toLong()
}
tag = "android"
created = getCurrentDateWithString()
timeFrame = DateUtil.getTimeFrame(System.currentTimeMillis())
isReview = false
duration = totalUseTime.value!!
totalDuration = totalUseTime.value!!
})
}
emitter.onNext(DataRepository.saveRecord(record))
emitter.onComplete()
}.compose(diskIo2Main()).subscribe {
lesson.videoPlayTime = currentPlayTime
LogUtil.e("当前播放时间:$currentPlayTime")
when{
//本课时已完成,只发送消息更新进度点即可
lesson.learnIsOver -> {
//发送数据事件
LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_DATA)
.post(LearnEventData(lesson.subjectId, lesson.courseId,
AppConstants.DATA_LESSON_LEARN_OVER).apply {
this.leesonPositionIndex = lesson.lessonPositionInList
isOnlyUpdatePoint = true
})
}
//本次播放完成,也为第一次播放完成,需要发送消息,重新计算进度
isCompletion -> {
//更新lesson
lesson.learnIsOver = true
//发送数据事件
LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_DATA)
.post(LearnEventData(lesson.subjectId, lesson.courseId,
AppConstants.DATA_LESSON_LEARN_OVER).apply {
this.leesonPositionIndex = lesson.lessonPositionInList
isOnlyUpdatePoint = false
})
}
}
uploadDate.value = true
}
}
private fun getCurrentDateWithString() = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1)
/* *//**
* 上传学习时间
* 视频播放进度点
*//*
fun upData() : MutableLiveData<Boolean>? {
val result = MutableLiveData<Boolean>()
if (!UserInfoManager.getInstance().isOfficialAccount()) {
dataToBack() //数据返回
result.postValue(true)
return result
}
if (!isLearn) { //没有播放视频
result.postValue(true)
return result
}
getLoadingDialog().postValue(true)
val subjectComposition : Long = SyncStateContract.Constants.SUBJECT_CHINESE
val coursePackId : Long = getLesson().getCoursePackId()
val courseId : Long = getLesson().getCourseId()
val videoId : Long = mVideoBean.getVideoId()
//上传时间、视频进度点,用于列表选中位置
val mqPush = Observable.fromCallable(Callable<Boolean?> { //创建记录 : 只保存进度点
val learnedEntiry = Struct.LearnEntity.newBuilder()
.setProjectId(subjectComposition)
.setPackId(coursePackId)
.setCourseId(courseId)
.setChapterId(getLesson().getChapterId())
.setLessonId(getLesson().getLessonId())
.setCreated(DateUtil.format(ServiceDateManager.getInstance().getTimeForLongMillSecond(), DateUtil.FORMAT_5))
.setEntityId(videoId)
.setIsOnlySavePoint(true)
.build()
val record = Struct.Record.newBuilder().addEntity(learnedEntiry).addDuration(saveCurrentLearnDuration())
MqRepository.getInstance().syncUpdate(this@ActivityVideoViewModel, record)
})
//设置视频播放点
val setVideoPointRequest = AppApi.SetVideoPointRequest.newBuilder()
.setProjectId(subjectComposition)
.setPackId(coursePackId)
.setCourseId(courseId)
.setVideoId(videoId)
.setTime((currentPlayTime / 1000).toString())
.build()
val emptyObservable : Observable<Empty> = DataRepository.getInstance().setVideoPoint(setVideoPointRequest)
var observable : Observable<Boolean?>? = null
observable = if (isCompletion && !getLesson().isFirstLearnIsOver()) {
//视频播放完成解锁
val emptyObservable1 : Observable<Empty> = DataRepository.getInstance()
.setUnLock(SyncStateContract.Constants.SUBJECT_CHINESE, coursePackId, courseId, getLesson().getChapterId(),
getLesson().getLessonId())
Observable.zip(emptyObservable1, mqPush, emptyObservable, { empty, aBoolean, empty2 -> aBoolean })
} else {
Observable.zip(mqPush, emptyObservable, { aBoolean, empty -> aBoolean })
}
observable.compose(RxHelper.observableIO2Main()).subscribe(Consumer<R> { b : R ->
if (b) {
dataToBack() //数据返回
}
getLoadingDialog().postValue(false)
result.postValue(b)
}, Consumer { throwable : Throwable? ->
getLoadingDialog().postValue(false)
result.postValue(false)
})
return result
}
*//** 发送动作返回进行统计 *//*
private fun dataToBack() {
//修改lesson学习数据
val learnLessson : CompositionLesson = getLesson()
learnLessson.setFirstLearnIsOver(learnLessson.isFirstLearnIsOver() || isCompletion) //第一次是否学习完成
learnLessson.setPlayTime((currentPlayTime / 1000).toInt())
if (learnLessson.isFirstLearnIsOver()) {
learnLessson.setLessonProgress(1)
} else {
learnLessson.setLessonProgress(learnLessson.getPlayTime() as Double / learnLessson.getTotalTime())
}
//发送数据进行更新
LiveDataBus.dataVideoBack().post(learnLessson)
}
*//**
* 保存当前的单词学习时长
*//*
private fun saveCurrentLearnDuration() : Struct.LearnDuration.Builder? {
return Struct.LearnDuration.newBuilder()
.setProjectId(SyncStateContract.Constants.SUBJECT_CHINESE)
.setPackId(getLesson().getCoursePackId())
.setCourseId(getLesson().getCourseId())
.setCreated(DateUtil.format(timeForLong, DateUtil.FORMAT_5))
.setTag(clientTag)
.setTimeFrame(DateUtil.getTimeFrame(timeForLong)) //设置时段
.setIsReview(false) //是否复习时长
.setDuration(getTotalUseTime()) //时长,毫秒
.setTotalDuration(getTotalUseTime()) //总时间,包含空闲部分
.setCategoryId(getLesson().getCategoryId())
}
*/
}

+ 10
- 7
app/src/main/java/com/xkl/cdl/module/learn/LearnExamViewModel.kt View File

@@ -13,6 +13,7 @@ import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.DataTransferHolder
import com.xkl.cdl.data.bean.SpellItemBean
import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.bean.intentdata.ExamData
import com.xkl.cdl.data.event.LearnEventData
import com.xkl.cdl.data.manager.db.DbControlBase
@@ -647,16 +648,11 @@ class LearnExamViewModel : LearnBaseViewModel() {
Observable.create<Boolean> {
viewModelScope.launch {
delay(1000)
DataRepository.saveRecord(record)
it.onNext(true)
it.onComplete()
}
// TODO: 2022/4/14 传递保存record信息
DataRepository.saveRecord(record)
// record 已经实例化并已经将数据保存
// LogUtil.e(JsonFormat.printToString(record.build()))
//知识点测试,标记当前课时为完成
if (intentData.examType == AppConstants.TEST_TYPE_COMPOSITION){
intentData.lesson!!.learnIsOver = true
}
}.compose(diskIo2Main()).subscribe({
showHideLoading(false)
@@ -711,11 +707,18 @@ class LearnExamViewModel : LearnBaseViewModel() {
})
}
AppConstants.TEST_TYPE_COMPOSITION -> { //知识点测试结束发送数据,当做学习完成处理
LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_DATA).post(LearnEventData(
intentData.subjectId,
intentData.courseId,
AppConstants.DATA_LESSON_LEARN_OVER).apply {
leesonPositionIndex = intentData.lesson!!.lessonPositionInList
if (intentData.lesson!!.learnIsOver){ //以及测试完成
isOnlyUpdatePoint = true
}else{ // 标记当前课时为完成, 用于计算进度
intentData.lesson!!.learnIsOver = true
}
})
}
}

+ 21
- 10
app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseLessonFragment.kt View File

@@ -24,6 +24,7 @@ import com.xkl.cdl.dialog.CommonDialogBean
import com.xkl.cdl.dialog.LearnDialog
import com.xkl.cdl.module.learn.LearnCReadingActivity
import com.xkl.cdl.module.learn.LearnCTaskActivity
import com.xkl.cdl.module.learn.LearnCVideoActivity
import com.xkl.cdl.module.learn.LearnWordActivity

/**
@@ -118,11 +119,14 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM
//视频
AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> vm.courseDetail.run {
val relationId = it.wordIds[0]
//更新视频播放点
vp[relationId] = learnEventData.videoPlayTime
//更新视频播放时间
vp[relationId] = "${it.videoPlayTime}"
//更新课程学习点
lesson_learn_point[key] = relationId //更新课时学习点
course_learn_point = "${key}_${relationId}" //更新课程学习点
lesson_learn_point[key] = if (it.learnIsOver) -relationId else relationId //更新课时学习点
course_learn_point = "${key}_${relationId}" //更新课程学习点
if (learnEventData.isOnlyUpdatePoint){
return@observe
}
}
//知识点
AppConstants.LESSON_TYPE_COMPOSITION_KNOWLEDGE -> vm.courseDetail.run {
@@ -133,13 +137,20 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM
}
//测试
AppConstants.LESSON_TYPE_COMPOSITION_EXAM -> vm.courseDetail.run {
course_learn_point = "${key}_0}" //更新课程学习点
if (it.learnIsOver) {
lesson_learn_point[key] = 0 //课时进度点
course_learn_point = "${key}_0}" //更新课程学习点
}
if (learnEventData.isOnlyUpdatePoint) {
return@observe
}
}
//课堂练习
AppConstants.LESSON_TYPE_COMPOSITION_READING -> vm.courseDetail.run {
val point = if (it.learnedIndex == -1) -1 else it.wordIds[it.learnedIndex]
lesson_learn_point[key] = point //更新课时学习点
course_learn_point = "${key}_$point" //更新课程学习点
//此entityId仅做记录,用于下次显示的时候,进行显示
val entityId = it.wordIds[it.learnedIndex]
lesson_learn_point[key] = if (it.learnIsOver) -entityId else entityId //更新课时学习点
course_learn_point = "${key}_$entityId" //更新课程学习点
if (learnEventData.isOnlyUpdatePoint){
return@observe
}
@@ -308,7 +319,6 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM
}
}
AppConstants.LESSON_TYPE_SENTENCE -> {
}
AppConstants.LESSON_TYPE_DIALOGUE -> {
@@ -316,7 +326,8 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM
}
//视频
AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> {
DataTransferHolder.instance.putData(value = entity)
startActivity(LearnCVideoActivity::class.java)
}
//知识点 : 直接进入学习,没有学前学后测试 判断学习是否完成,完成的情况下需要弹窗提示
AppConstants.LESSON_TYPE_COMPOSITION_KNOWLEDGE -> when{

+ 5
- 0
app/src/main/res/color/video_tab_color.xml View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="false" android:color = "#8A8A99"/>
<item android:state_checked="true" android:color = "#5082E6"/>
</selector>

+ 13
- 0
app/src/main/res/drawable/video_tab_bg.xml View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape android:shape="rectangle">
<solid android:color="@color/white"/>
</shape>
</item>
<item android:state_checked="false">
<shape android:shape="rectangle">
<solid android:color="@color/white_1"/>
</shape>
</item>
</selector>

+ 171
- 6
app/src/main/res/layout/activity_learn_cvideo.xml View File

@@ -1,9 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".module.learn.LearnCVideoActivity">
xmlns:tools="http://schemas.android.com/tools">

</androidx.constraintlayout.widget.ConstraintLayout>
<data>

</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".module.learn.LearnCVideoActivity"
android:background="@color/white">

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<!--播放器-->
<com.xkl.videoplayer.widget.PineMediaPlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="202dp"
android:layout_alignParentTop="true"
android:background="#121010"
android:gravity="center" />

<!--返回键,视频加载失败-->
<RelativeLayout
android:id="@+id/stop_view"
android:layout_width="match_parent"
android:layout_height="202dp"
android:background="@color/main_text_color"
android:visibility="gone"
tools:visibility="visible">

<ImageView
android:id="@+id/stop_back"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="20dp"
android:src="@drawable/pine_player_go_back_icon" />
<!--加载失败-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="18dp"
android:drawableLeft="@mipmap/sigh_icon"
android:drawablePadding="8dp"
android:text="加载失败~"
android:textColor="#fff7874f"
android:textSize="14sp" />

<TextView
android:id="@+id/reload_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="10dp"
android:text="重新加载"
android:textColor="#fff" />
</LinearLayout>

</RelativeLayout>

<!--全屏加载失败界面-->
<RelativeLayout
android:id="@+id/stop_view_full"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_text_color"
android:visibility="gone">

<ImageView
android:id="@+id/stop_back_full"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="20dp"
android:src="@drawable/pine_player_go_back_icon" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="18dp"
android:drawableLeft="@mipmap/sigh_icon"
android:drawablePadding="8dp"
android:text="加载失败~"
android:textColor="#fff7874f"
android:textSize="14sp" />

<TextView
android:id="@+id/reload_btn_full"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="10dp"
android:text="重新加载"
android:textColor="#fff"
/>
</LinearLayout>
</RelativeLayout>

</RelativeLayout>


<RadioGroup
android:id="@+id/radio_group"
android:layout_width="match_parent"
android:layout_height="38dp"
android:orientation="horizontal">

<com.suliang.common.widget.MyRadioButton
android:id="@+id/rb_video_blackboard"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/video_tab_bg"
android:button="@null"
android:checked="true"
android:drawableStart="@drawable/ic_video_blackboard"
android:drawablePadding="5dp"
android:gravity="center|center_vertical"
android:text="视频板书"
android:textColor="@color/video_tab_color"
android:drawableTint="@color/video_tab_color"/>


<com.suliang.common.widget.MyRadioButton
android:id="@+id/rb_video_note"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/video_tab_bg"
android:button="@null"
android:checked="false"
android:drawableStart="@drawable/ic_video_note"
android:drawablePadding="5dp"
android:gravity="center|center_vertical"
android:text="我的笔记"
android:textColor="@color/video_tab_color"
android:drawableTint="@color/video_tab_color"/>
</RadioGroup>


<!--列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/global_spacing" />


</LinearLayout>
</layout>

+ 3
- 3
app/src/main/res/layout/include_main_learn_center_course_type_title.xml View File

@@ -14,7 +14,7 @@
android:background="@drawable/shape_rounder_toplr_24_white"
tools:showIn="@layout/activity_course_main">

<com.suliang.common.widget.TextViewCenterDrawable
<TextView
android:id="@+id/tab_discern"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -44,7 +44,7 @@
android:visibility="gone" />


<com.suliang.common.widget.TextViewCenterDrawable
<TextView
android:id="@+id/tab_spell"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -73,7 +73,7 @@
android:src="@drawable/ic_arrow_right"
android:visibility="gone" />

<com.suliang.common.widget.TextViewCenterDrawable
<TextView
android:id="@+id/tab_voice"
android:layout_width="wrap_content"
android:layout_height="match_parent"

+ 5
- 2
app/src/main/res/layout/item_task_image.xml View File

@@ -7,8 +7,11 @@
</data>

<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
app:shapeAppearance="@style/roundedCornerStyle" />
app:shapeAppearance="@style/roundedCornerStyle"
android:layout_margin="4dp"
/>
</layout>

+ 25
- 0
app/src/main/res/layout/item_video_adapter.xml View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

</data>


<com.suliang.common.widget.TextViewDrawableTopCenterWithFirst
android:id="@+id/tv_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/global_spacing"
android:drawableStart="@drawable/ic_note"
android:drawablePadding="8dp"
android:text="1.高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情,避免不重要不紧急的事情"
android:textSize="@dimen/smallSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:includeFontPadding="false"/>

</layout>

BIN
app/src/main/res/mipmap-xxhdpi/sigh_icon.png View File


+ 2
- 0
app/src/main/res/values/styles.xml View File

@@ -45,4 +45,6 @@
<item name="android:backgroundDimEnabled">true</item> <!--模糊,背景透明是这个- 弹窗背景是否变暗 -->
</style>



</resources>

+ 6
- 0
app/src/main/res/values/themes.xml View File

@@ -30,6 +30,10 @@
<item name="android:windowBackground">@drawable/theme_splash_bg</item>
</style>

<style name="Theme.videoTheme" parent="Theme.XklLocal">
<item name="android:windowIsTranslucent">false</item> <!--影响了视频切换后横屏的有白边问题-->
</style>

<style name="Theme.XklLocal.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
@@ -39,4 +43,6 @@

<style name="Theme.XklLocal.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />



</resources>

+ 2
- 2
app/svg/drawable/ic_note.xml View File

@@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:width="16dp"
android:height="16dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path

+ 12
- 0
app/svg/drawable/ic_note_del.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2.4C17.3019,2.4 21.6,6.6981 21.6,12C21.6,17.3019 17.3019,21.6 12,21.6C6.6981,21.6 2.4,17.3019 2.4,12C2.4,6.6981 6.6981,2.4 12,2.4ZM14.7646,7.1732C14.7787,6.9677 14.4863,6.8579 14.3709,7.0604L14.3709,7.0604L11.9821,11.2439L10.5884,11.5498L10.5526,11.4079C10.5255,11.3137 10.492,11.2203 10.4509,11.1282C9.9592,10.0103 8.6539,9.5029 7.5351,9.9937C6.4163,10.4844 5.9099,11.7907 6.4025,12.9096L6.4025,12.9096L6.4712,13.0519C7.0074,14.0697 8.2482,14.5135 9.3183,14.0441L9.3183,14.0441L10.7386,13.4198L9.9686,14.7663L9.892,14.9126C9.3976,15.948 9.7791,17.2054 10.7921,17.7847C11.8525,18.3906 13.2028,18.0215 13.8086,16.9611C14.4145,15.9008 14.0464,14.5496 12.9851,13.9427L12.9851,13.9427L12.8527,13.8724C12.7634,13.829 12.6724,13.7926 12.5802,13.762L12.5802,13.762L13.0297,12.4119L17.4413,10.4704L17.4923,10.4401C17.654,10.3127 17.5387,10.0224 17.3111,10.0727L17.3111,10.0727L13.5322,10.9031L14.7528,7.2315ZM11.1325,15.4314C11.3715,15.0135 11.903,14.8686 12.3219,15.1067C12.7398,15.3457 12.8847,15.8772 12.6466,16.2961C12.4076,16.714 11.8761,16.8589 11.4572,16.6208C11.0384,16.3808 10.8935,15.8493 11.1325,15.4314ZM8.0768,11.2209C8.5174,11.0268 9.0317,11.2275 9.2257,11.6681C9.4197,12.1087 9.2191,12.623 8.7785,12.817C8.3379,13.011 7.8236,12.8104 7.6296,12.3698C7.4356,11.9292 7.6362,11.4149 8.0768,11.2209Z"
android:strokeWidth="1"
android:fillColor="#8A8A99"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 12
- 0
app/svg/drawable/ic_video_blackboard.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="19dp"
android:height="18dp"
android:viewportWidth="19"
android:viewportHeight="18">
<path
android:pathData="M9.0843,1.3757C9.3205,1.1414 9.7035,1.1414 9.9397,1.3757L9.9397,1.3757L12.7864,4.199L14.9552,4.2C15.9072,4.2 16.6879,4.9273 16.7636,5.8524L16.7696,6L16.7696,13.2C16.7696,14.1941 15.9573,15 14.9552,15L14.9552,15L4.0688,15C3.0667,15 2.2544,14.1941 2.2544,13.2L2.2544,13.2L2.2544,6C2.2544,5.0059 3.0667,4.2 4.0688,4.2L4.0688,4.2L6.2364,4.199ZM6.4757,5.3999L4.0688,5.4C3.7348,5.4 3.464,5.6686 3.464,6L3.464,6L3.464,13.2C3.464,13.5314 3.7348,13.8 4.0688,13.8L4.0688,13.8L14.9552,13.8C15.2892,13.8 15.56,13.5314 15.56,13.2L15.56,13.2L15.56,6C15.56,5.6686 15.2892,5.4 14.9552,5.4L14.9552,5.4L12.5474,5.399L12.536,5.4L6.488,5.4C6.4839,5.4 6.4798,5.4 6.4757,5.3999ZM9.2096,10.2C9.5436,10.2 9.8144,10.4686 9.8144,10.8C9.8144,11.1077 9.5809,11.3613 9.2801,11.396L9.2096,11.4L5.5808,11.4C5.2468,11.4 4.976,11.1314 4.976,10.8C4.976,10.4923 5.2095,10.2387 5.5103,10.204L5.5808,10.2L9.2096,10.2ZM13.4432,7.5C13.7772,7.5 14.048,7.7686 14.048,8.1C14.048,8.4077 13.8145,8.6613 13.5137,8.696L13.4432,8.7L5.5808,8.7C5.2468,8.7 4.976,8.4314 4.976,8.1C4.976,7.7923 5.2095,7.5387 5.5103,7.504L5.5808,7.5L13.4432,7.5ZM9.512,2.649L7.9484,4.199L11.0744,4.199L9.512,2.649Z"
android:strokeWidth="1"
android:fillColor="#5082E6"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 12
- 0
app/svg/drawable/ic_video_note.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="18dp"
android:viewportWidth="20"
android:viewportHeight="18">
<path
android:pathData="M16.4602,13.9729C16.7942,13.9729 17.065,14.2415 17.065,14.5729C17.065,14.8806 16.8316,15.1342 16.5308,15.1688L16.4602,15.1729L4.3642,15.1729C4.0302,15.1729 3.7594,14.9042 3.7594,14.5729C3.7594,14.2652 3.9929,14.0116 4.2937,13.9769L4.3642,13.9729L16.4602,13.9729ZM11.2022,2.1485C11.9051,1.4456 13.0494,1.4501 13.7579,2.1587L13.7579,2.1587L15.0409,3.4417C15.7495,4.1503 15.754,5.2945 15.0511,5.9975L15.0511,5.9975L8.9655,12.0831C8.6942,12.3543 8.3427,12.5311 7.9622,12.5877L7.9622,12.5877L5.6457,12.9322C4.9883,13.0299 4.3719,12.5756 4.2689,11.9174C4.2501,11.7969 4.2496,11.6743 4.2675,11.5539L4.2675,11.5539L4.6119,9.2374C4.6685,8.8569 4.8453,8.5054 5.1166,8.2341L5.1166,8.2341ZM12.9094,3.0072C12.6732,2.7711 12.2918,2.7695 12.0575,3.0038L12.0575,3.0038L5.9719,9.0895C5.8815,9.1799 5.8225,9.297 5.8037,9.4239L5.8037,9.4239L5.4592,11.7404L7.7758,11.396C7.9026,11.3771 8.0198,11.3182 8.1102,11.2277L8.1102,11.2277L14.1958,5.1421C14.4301,4.9078 14.4286,4.5264 14.1924,4.2902L14.1924,4.2902Z"
android:strokeWidth="1"
android:fillColor="#5082E6"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 3
- 3
lib/common/src/main/java/com/suliang/common/base/adapter/BaseRVAdapter.kt View File

@@ -81,11 +81,11 @@ abstract class BaseRVAdapter<T> :
return DataBindingUtil.inflate(LayoutInflater.from(parent.context),layoutRes,parent,false)
}


@Synchronized
fun getItem(position: Int) : T {
open fun getItem(position: Int) : T {
return mData.get(position)
}
fun getData(): MutableList<T>{
return mData
}

+ 57
- 7
lib/common/src/main/java/com/suliang/common/util/DateUtil.kt View File

@@ -1,9 +1,9 @@
package com.suliang.common.util

import android.annotation.SuppressLint
import android.text.TextUtils
import java.text.SimpleDateFormat
import java.util.*
import java.util.logging.SimpleFormatter

/**
* author suliang
@@ -16,7 +16,7 @@ class DateUtil {
val FORMAT_1 = "yyyy-MM-dd HH:mm:ss"
val FORMAT_2 = "HH:mm:ss"
val FORMAT_3 = "HH时mm分ss"
/**
* 设置时区从0开始的,正时,可格式化时分秒
* @param formatValue Long 需要格式化的值
@@ -25,14 +25,13 @@ class DateUtil {
*/
@SuppressLint("SimpleDateFormat")
@JvmStatic
fun formatGMT(formatValue : Long, format : String): String {
return SimpleDateFormat(format).apply {
fun formatGMT(formatValue : Long, format : String) : String {
return SimpleDateFormat(format).apply {
timeZone = TimeZone.getTimeZone("GMT+00:00")
}.format(formatValue)
}
/**
* 格式化时间戳为字符串样式
* @param formatValue Long 时间戳
@@ -41,10 +40,10 @@ class DateUtil {
*/
@SuppressLint("SimpleDateFormat")
@JvmStatic
fun format (formatValue : Long, format:String):String{
fun format(formatValue : Long, format : String) : String {
return SimpleDateFormat(format).format(formatValue)
}
/**
* 获取时间段
* @param timeForLong 毫秒时间
@@ -63,7 +62,58 @@ class DateUtil {
else -> 4
}
}
/**
* 将字符串格式的时间,转为秒数
* @param string String 字符串格式 HH:mm:ss
* @return Long 秒数
*/
fun getTimeSecond(time_point : String) : Long {
var hourSecond = 0
var minuteSecond = 0
var second = 0
if (!TextUtils.isEmpty(time_point)) {
val split = time_point.split(":").map {
it.toInt()
}
when (split.size) {
1 -> second = split[0]
2 -> {
minuteSecond = split[0] * 60
second = split[1]
}
3 -> {
hourSecond = split[0] * 3600
minuteSecond = split[1] * 60
second = split[2]
}
}
}
return (hourSecond + minuteSecond + second).toLong()
}
/**
* 秒数格式化 00:00:00 00:00
* @param second 秒数
* @return
*/
fun formatSeconds(second : Long) : String {
return when {
second <= 0 -> {
"00:00"
}
second < 60 -> {
String.format(Locale.getDefault(), "00:%02d", second)
}
second < 3600 -> {
String.format(Locale.getDefault(), "%02d:%02d", second / 60, second % 60)
}
else -> {
String.format(Locale.getDefault(), "%02d:%02d:%02d", second / 3600, second % 3600 / 60, second % 60)
}
}
}
}
}

+ 15
- 3
lib/common/src/main/java/com/suliang/common/util/DrawableUti.kt View File

@@ -7,6 +7,7 @@ import android.graphics.Canvas
import android.graphics.PixelFormat
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.NonNull
@@ -102,10 +103,21 @@ class DrawableUti {
val oldBitmap = vectorDrawableCompat.toBitmap(scale*vectorDrawableCompat.intrinsicWidth,scale*vectorDrawableCompat.intrinsicHeight,Bitmap.Config.ARGB_8888)
return BitmapDrawable(resource,oldBitmap)
// val newBitmap = Bitmap.createScaledBitmap(oldBitmap,scale*oldBitmap.width,scale*oldBitmap.height,true)
}
@JvmStatic
fun changeTextViewDrawableColor(view: TextView, svgColor:Int){
view.compoundDrawables.forEachIndexed{ index: Int, drawable: Drawable? ->
drawable?.let {
when(index){
0 -> view.setCompoundDrawablesWithIntrinsicBounds(tintDrawable(it,svgColor),null,null,null)
1 -> view.setCompoundDrawablesWithIntrinsicBounds(null,tintDrawable(it,svgColor),null,null)
2 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,tintDrawable(it,svgColor),null)
3 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,null,tintDrawable(it,svgColor))
}
}
}
}
}
}

+ 4
- 0
lib/common/src/main/java/com/suliang/common/util/LogUtil.kt View File

@@ -27,6 +27,10 @@ object LogUtil {
fun e(message: String) {
log("e",message)
}
fun e(tag:String,message: String) {
log("e", "$tag $message")
}
fun i(message: String) {
log("i",message)
}

+ 3
- 3
lib/common/src/main/java/com/suliang/common/util/thread/AppExecutors.kt View File

@@ -44,7 +44,7 @@ object AppExecutors {
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
CommonLog.e(TAG,"rejectedExecution: scheduled executor queue overflow");
LogUtil.e(TAG,"rejectedExecution: scheduled executor queue overflow");
}
});
}*/
@@ -62,7 +62,7 @@ object AppExecutors {
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
CommonLog.e(TAG,"rejectedExecution: diskIo executor queue overflow");
LogUtil.e(TAG,"rejectedExecution: diskIo executor queue overflow");
}
});
}
@@ -78,7 +78,7 @@ object AppExecutors {
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
CommonLog.e(TAG,"rejectedExecution: network executor queue overflow");
LogUtil.e(TAG,"rejectedExecution: network executor queue overflow");
}
});
}

+ 51
- 0
lib/common/src/main/java/com/suliang/common/widget/MyRadioButton.java View File

@@ -0,0 +1,51 @@
package com.suliang.common.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;

import androidx.annotation.Nullable;

/**
* author suliang
* create 2021/4/12 9:27
* Describe: 自定义 RadioButton 左侧图标紧挨着文字
*/
public class MyRadioButton extends androidx.appcompat.widget.AppCompatRadioButton {

public MyRadioButton(Context context) {
super(context);
}

public MyRadioButton(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

public MyRadioButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onDraw(Canvas canvas) {
Drawable[] compoundDrawables = getCompoundDrawables();

//左侧Drawable
Drawable drawableLeft = compoundDrawables[0];
if(drawableLeft != null){

float textWidth = getPaint().measureText(getText().toString()); //文字宽度

int drawableWidth = drawableLeft.getIntrinsicWidth() ; //drawable图片宽度
int drawableHeight = drawableLeft.getIntrinsicHeight() ; //drawable高度
int drawablePadding = getCompoundDrawablePadding(); //设置的间距

int left = (int) ((this.getWidth() - drawableWidth - drawablePadding - textWidth) / 2);
drawableLeft.setBounds(left, 0, left + drawableWidth, drawableHeight); //设置drawableLeft区域
}
//指定drawable显示的大小
setCompoundDrawables(drawableLeft, compoundDrawables[1], compoundDrawables[2], compoundDrawables[3]);

super.onDraw(canvas);
}
}

+ 3
- 0
lib/common/src/main/java/com/suliang/common/widget/TextViewCenterDrawable.java View File

@@ -8,6 +8,9 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;

/**
* 文字与drawable都居中的TextView
*/
@SuppressLint("AppCompatCustomView")
public class TextViewCenterDrawable extends TextView {


+ 40
- 0
lib/common/src/main/java/com/suliang/common/widget/TextViewDrawableTopCenterWithFirst.kt View File

@@ -0,0 +1,40 @@
package com.suliang.common.widget

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import java.lang.reflect.Constructor
import android.graphics.drawable.Drawable

/**
* author suliang
* create 2021/11/17 16:28
* Describe: left right drawable 与第一行居中的TextView
* https://www.jianshu.com/p/27b823faf3ee
*/
class TextViewDrawableTopCenterWithFirst @JvmOverloads constructor(context : Context,
attr : AttributeSet?,
defStyleAttr : Int = 0) :
AppCompatTextView(context, attr, defStyleAttr) {
override fun onLayout(changed : Boolean, left : Int, top : Int, right : Int, bottom : Int) {
super.onLayout(changed, left, top, right, bottom)
handleLeftDrawable()
}
private fun handleLeftDrawable() {
compoundDrawables[0]?.let {
//实际行数
val lineCount = Math.min(lineCount, maxLines)
//文本高度
val vSpace = bottom - top - compoundPaddingBottom - compoundPaddingTop
//计算位置差值
val verticalOffset : Int = (-1 * (vSpace * (1 - 1.0f / lineCount)) / 2).toInt()
//重设bounds
it.setBounds(0, verticalOffset, it.intrinsicWidth, it.intrinsicHeight + verticalOffset)
}
}
}

+ 1
- 0
settings.gradle View File

@@ -12,3 +12,4 @@ dependencyResolutionManagement {
rootProject.name = "XklLocal"
include ':app'
include ':lib:common'
include ':videoplayer'

+ 10
- 0
videoplayer/src/main/res/values/styles.xml View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<!--圆角图片-->
<style name="roundedCornerStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">4dp</item>
</style>

</resources>

Loading…
Cancel
Save