Commit baf849fe by 刘鹏

Merge branch 'feat/wjy/audioPlay' into 'd/v4.4.07'

课程音频优化

See merge request app_android_lib/YDL-Component!299
parents 200515d3 7e53803d
......@@ -215,6 +215,7 @@ dependencies {
implementation project(":api:im")
implementation project(':m-dynamic')
implementation project(':m-course')
implementation project(":api:course")
implementation project(':m-article')
implementation project(':m-audioim')
implementation project(':m-fm')
......
......@@ -16,6 +16,9 @@ import com.umeng.socialize.PlatformConfig;
import com.umeng.socialize.UMShareAPI;
import com.ydl.component.route.PlatformTempCommonRouteImpl;
import com.ydl.confide.BuildConfig;
import com.ydl.media.audio.AudioPlayer;
import com.ydl.media.audio.manager.MediaSessionManager;
import com.ydl.media.audio.manager.NotifyManager;
import com.ydl.ydl_image.manager.YDLImageCacheManager;
import com.ydl.ydlcommon.base.BaseApp;
import com.ydl.ydlcommon.router.YdlCommonRouterManager;
......@@ -26,6 +29,7 @@ import com.yidianling.common.tools.ToastUtil;
import com.yidianling.consultant.preview.TestImageLoader;
import com.yidianling.consultant.preview.ZoomMediaLoader;
import com.yidianling.course.lifeCallback.CoursePlayLifecycle;
import com.yidianling.course.widget.AudioPlayView;
/**
......@@ -46,6 +50,9 @@ public class ComponentTestApp extends BaseApp {
public void onCreate() {
super.onCreate();
mComponentTestAppContext = this;
AudioPlayer.Companion.get().init(this);
NotifyManager.Companion.get().init(this);
MediaSessionManager.Companion.get().init(this);
LogUtil.debug = BuildConfig.DEBUG;
com.ydl.ydlcommon.utils.LogUtil.debug = BuildConfig.DEBUG;
......
......@@ -22,7 +22,6 @@ import com.ydl.media.view.PlayTypeEnum;
import com.ydl.media.view.PlayerFloatHelper;
import com.ydl.ydlcommon.utils.LogUtil;
import com.yidianling.common.tools.ToastUtil;
import com.yidianling.muse.event.MeditationFloatEvent;
import java.util.HashMap;
import java.util.Locale;
......@@ -92,17 +91,22 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
}
@Override
public void onChange(Music music) {
public boolean onPreLoad(int playPosition) {
return true;
}
@Override
public void onLoad(Music music) {
onChangeImpl(music);
}
@Override
public void onPlayerStart() {
public void onStartPlay() {
ivPlay.setSelected(true);
}
@Override
public void onPlayerPause() {
public void onPausePlay() {
ivPlay.setSelected(false);
}
......@@ -147,7 +151,7 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
}
private void showFloatView() {
if(!PlayerFloatHelper.Companion.isShow(getActivity())) {
if(!PlayerFloatHelper.Companion.isShow()) {
PlayerFloatHelper.Companion.show(getActivity(), PlayTypeEnum.PLAY_TYPE_FM,new HashMap<>());
}else {
PlayerFloatHelper.Companion.showIfPlaying(getActivity());
......@@ -191,7 +195,7 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
tvTitle.setText(music.getTitle());
tvArtist.setText(music.getArtist());
sbProgress.setProgress((int) AudioPlayer.Companion.get().getAudioPosition());
sbProgress.setProgress((int) AudioPlayer.Companion.get().getCurrentPosition());
sbProgress.setSecondaryProgress(0);
// sbProgress.setMax((int) AudioPlayer.Companion.get().getDuration());
......@@ -207,7 +211,7 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
}
void play() {
AudioPlayer.Companion.get().playPause();
AudioPlayer.Companion.get().playOrPause();
}
void next() {
......@@ -263,7 +267,7 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
@Override
public void onDestroy() {
AudioPlayer.Companion.get().removeOnPlayEventListener(this);
PlayerFloatHelper.Companion.onDestroy();
PlayerFloatHelper.Companion.removeResetView();
super.onDestroy();
}
......
......@@ -14,7 +14,7 @@ buildscript {
ydlrouter_version = '1.2.3'
constrait_support_version = '1.0.2'
componentVersion = "0.3.0.12"
componentVersion = "0.3.0.18"
}
repositories {
mavenCentral()
......
......@@ -148,7 +148,7 @@ class ConfideHomeEventImpl(context: Context, var confideHomeView: IConfideHomeCo
override fun videoShowClick(index: Int, data: List<ConfideHomeBodyBean>?) {
val dataJson = if (data != null) JSON.toJSONString(data) else null
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
PlayerFloatHelper.updatePlayState()
route(mContext, ConfideRoute.R_VIDEO_SHOW, "initPos" to index, "initData" to dataJson)
}
......@@ -184,9 +184,9 @@ class ConfideHomeEventImpl(context: Context, var confideHomeView: IConfideHomeCo
mu.path = playUrl
mu.coverPath=confideIcon
AudioPlayer.get().singlePlay(mu)
if (PlayerFloatHelper.isShow(mContext!!)) {
if (PlayerFloatHelper.isShow()) {
if (AudioPlayer.get().playMode.value() == 1) {
PlayerFloatHelper.removeView(mContext!!)
PlayerFloatHelper.removeView()
PlayerFloatHelper.show(mContext!!)
} else {
......@@ -255,7 +255,7 @@ class ConfideHomeEventImpl(context: Context, var confideHomeView: IConfideHomeCo
if (TextUtils.isEmpty(cachePlayUrl)) {
return
}
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
confideHomeView.updataPlayStatu(cacheType, cacheIndex!!, cacheSubIndex, true)
}
......@@ -263,16 +263,14 @@ class ConfideHomeEventImpl(context: Context, var confideHomeView: IConfideHomeCo
* 暂停播放音频文件
*/
override fun pauseVoice() {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
PlayerFloatHelper.updatePlayState()
//因为9999 类型不存在 所以就能把状态全部置为暂停状态了 偷个懒
confideHomeView.updataPlayStatu(9999, 0, 0, false)
}
override fun destoryPlayer() {
PlayerFloatHelper.hide()
PlayerFloatHelper.playTempData = hashMapOf<String,String>()
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
floatViewClickListener?.let { PlayerFloatHelper.removeClickListener(it) };
}
......
......@@ -14,6 +14,7 @@ tv.danmaku.ijk.media.player_arm64
"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<!--课程首页-->
......@@ -38,6 +39,12 @@ tv.danmaku.ijk.media.player_arm64
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/CourseTheme" />
<activity
android:name=".coursePlay.AudioPlayActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/CourseTheme" />
<!--课程课后笔记列表-->
<activity
android:name=".coursePlay.CourseReplyListActivity"
......
......@@ -60,6 +60,6 @@ public class CourseActivity extends BaseActivity {
@Override
protected void onDestroy() {
super.onDestroy();
PlayerFloatHelper.Companion.onDestroy();
PlayerFloatHelper.Companion.removeResetView();
}
}
......@@ -5,12 +5,12 @@ import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Route
import com.ydl.ydlcommon.actions.share.ShareUtils
import com.ydl.ydlcommon.base.BaseActivity
......
package com.yidianling.course.bean
/**
* 咨询师上线状态
*/
data class CourseConsultant(var doctorId: Int,
var doctorUid: Int,
var doctorName: String,
var doctorHead: String,
var isOnline: Int // 1在线 0不在线
)
\ No newline at end of file
......@@ -7,6 +7,8 @@ package com.yidianling.course.bean
* @Company 壹点灵
* @date 2019/7/11
*/
const val COURSE_AUDIO = 1
const val COURSE_VIDEO = 2
data class CourseMediaBean(
/**
......@@ -19,6 +21,8 @@ data class CourseMediaBean(
var doctorName: String,
/**
* 资料类型 1.音频 2.视频 ,
* @see COURSE_AUDIO
* @see COURSE_VIDEO
*/
var mediaType: Int,
/**
......
......@@ -738,9 +738,7 @@ class CourseListContainerActivity : BaseActivity(), PtrHandler, LoadMoreHandler
override fun onDestroy() {
super.onDestroy()
if (!PlayerFloatHelper.isCanClick) {
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(this)
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
}
}
......
......@@ -8,7 +8,6 @@ import android.graphics.Color
import android.os.Build
import android.os.Handler
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.OrientationHelper
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
......@@ -27,7 +26,6 @@ import com.ydl.media.audio.utils.PlayProgressUtil
import com.ydl.media.view.PlayTypeEnum
import com.ydl.media.view.PlayerFloatHelper
import com.ydl.ydl_image.module.GlideApp
import com.ydl.ydl_router.manager.YDLRouterManager
import com.ydl.ydlcommon.adapter.CommonRecyclerAdapter
import com.ydl.ydlcommon.base.BaseActivity
import com.ydl.ydlcommon.bean.ShareData
......@@ -247,32 +245,6 @@ class CoursePlayActivity : BaseActivity() {
)
updatePlayingListStatus(position)
// if (playList[index].mediaType == 2) {
// play_type = 1
// (playVideoView as CoursePlayItemViewVideo)?.play(position)
// }
// if (playList[index].mediaType == 1) {
// play_type = 0
// if (RxNetTool.isWifi(this@CoursePlayActivity)) {
// (playAudioView as CoursePlayItemViewAudio).playView!!.play(
// position
// )
// } else {
// if ((playAudioView as CoursePlayItemViewAudio).hasEnsureNetStatus) {
// (playAudioView as CoursePlayItemViewAudio).playView!!.play(
// position
// )
// } else {
// (playAudioView as CoursePlayItemViewAudio).setData(
// position,
// playList,
// courPlayBean!!.courseExtra,
// from
// )
// }
// }
// }
// adapter?.notifyDataSetChanged()
}
}
}
......@@ -420,12 +392,12 @@ class CoursePlayActivity : BaseActivity() {
try {
if (play_type == 1) {
//视频播放销毁音频服务
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
if (PlayerFloatHelper.playingType == PlayTypeEnum.PLAY_TYPE_CONFIDE || PlayerFloatHelper.playingType == PlayTypeEnum.PLAY_TYPE_FM) {
if (AudioPlayer.get().isPlaying) {
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
}
......@@ -512,7 +484,7 @@ class CoursePlayActivity : BaseActivity() {
if (index < playList.size && this.index != index) {
if (!playList[index].isDemo && !courPlayBean!!.courseExtra.isBuy) {
if (AudioPlayer.get().isPlaying) {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
if (videoView != null) {
......@@ -549,7 +521,7 @@ class CoursePlayActivity : BaseActivity() {
frame_video_play.visibility = View.VISIBLE
frame_audio_play.visibility = View.GONE
if (AudioPlayer.get().isPlaying) {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
(playVideoView as CoursePlayItemViewVideo)?.play(index)
}
......@@ -650,8 +622,8 @@ class CoursePlayActivity : BaseActivity() {
playVideoView?.onResume()
playAudioView?.onResume()
if (PlayerFloatHelper.isShow(this)) {
PlayerFloatHelper.removeView(this)
if (PlayerFloatHelper.isShow()) {
PlayerFloatHelper.removeView()
}
}
......
package com.yidianling.course.coursePlay.model
import com.ydl.ydlcommon.data.http.BaseResponse
import com.ydl.ydlcommon.mvp.base.BaseModel
import com.yidianling.course.bean.CourseConsultant
import com.yidianling.course.bean.CourseMediaDetailBean
import com.yidianling.course.coursePlay.presenter.IAudioPlayContract
import com.yidianling.course.net.CourseRetrofitUtils
import io.reactivex.Observable
class AudioPlayModelImpl : BaseModel(), IAudioPlayContract.Model {
override fun getCoursePlayData(courseId: Int): Observable<BaseResponse<CourseMediaDetailBean>> {
return CourseRetrofitUtils.getCoursePlayData(courseId.toString())
}
override fun getConsultantInfo(courseId: Int): Observable<BaseResponse<CourseConsultant>> {
return CourseRetrofitUtils.getConsultantInfo(courseId.toString())
}
}
\ No newline at end of file
package com.yidianling.course.coursePlay.presenter
import android.content.Intent
import android.text.TextUtils
import com.ydl.media.audio.model.Music
import com.ydl.media.view.PlayTypeEnum
import com.ydl.media.view.PlayerFloatHelper
import com.ydl.ydlcommon.data.http.BaseResponse
import com.ydl.ydlcommon.data.http.RxUtils
import com.ydl.ydlcommon.modular.findRouteService
import com.ydl.ydlcommon.mvp.base.BasePresenter
import com.ydl.ydlcommon.router.IYDLRouterConstant
import com.ydl.ydlcommon.router.YdlCommonRouterManager
import com.ydl.ydlcommon.utils.extend.ifNotNull
import com.ydl.ydlnet.client.observer.CommonObserver
import com.yidianling.common.tools.ToastUtil
import com.yidianling.course.bean.COURSE_AUDIO
import com.yidianling.course.bean.CourseConsultant
import com.yidianling.course.bean.CourseMediaBean
import com.yidianling.course.bean.CourseMediaDetailBean
import com.yidianling.course.coursePlay.model.AudioPlayModelImpl
import com.yidianling.user.api.service.IUserService
import org.json.JSONException
import org.json.JSONObject
import java.util.*
class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayContract.Model>(),
IAudioPlayContract.Presenter {
private val ROUTER_PARAMS = "routerParam"
private var mFrom: Int = 0
private var mPlayUrl: String? = null // 外部选中要播放的音频文件地址
private var mCourPlayBean: CourseMediaDetailBean? = null
private val mPlayList: ArrayList<CourseMediaBean> = ArrayList() // 播放列表
private var mCurrentPosition = 0
private var mCourseId = 0 // 课程id
override fun createModel(): IAudioPlayContract.Model = AudioPlayModelImpl()
override fun getParams(intent: Intent) {
if (intent.hasExtra(ROUTER_PARAMS)) {
//路由传递的入参
val json = intent.getStringExtra(ROUTER_PARAMS)
try {
val jsonObject = JSONObject(json)
//课程ID
mCourseId = jsonObject.getInt(IYDLRouterConstant.EXTRA_ID)
} catch (e: JSONException) {
e.printStackTrace()
}
return
}
//正常跳转所传参数
mCourseId = intent.getIntExtra("course_id", 0)
mPlayUrl = intent.getStringExtra("coursePlayUrl")
val isFromFloatView = intent.getBooleanExtra("isFromFloatView", false)
mView.setNonWifiTips(!isFromFloatView)
mFrom = intent.getIntExtra("from", 0)
if (mCourseId == 0) {
ToastUtil.toastShort("参数错误")
mView.close()
}
}
override fun getNewIntent(intent: Intent) {
val courseId = intent.getIntExtra("course_id", 0)
if (0 == courseId) {
ToastUtil.toastShort("参数错误")
mView.close()
} else if (courseId != mCourseId) {
mCourseId = courseId
getCoursePlayData()
}
}
override fun getCoursePlayData() {
mView.showProgressDialog()
mModel.getCoursePlayData(mCourseId)
.compose(RxUtils.applySchedulers(mView))
.subscribe(object : CommonObserver<BaseResponse<CourseMediaDetailBean>>() {
override fun onError(s: String?) {
mView.dismissProgressDialog()
}
override fun onSuccess(resp: BaseResponse<CourseMediaDetailBean>?) {
mView.dismissProgressDialog()
resp?.let { response ->
if (response.code == 200) {
response.data?.let { bean ->
mCourPlayBean = bean
setPlayList(bean)
if (!mPlayUrl.isNullOrEmpty()) {
val position = mPlayList.indexOfFirst {
mPlayUrl == it.url
}
mCurrentPosition = if (-1 == position) 0 else position
}
mView.updateView(bean.courseExtra, mFrom)
} ?: let {
ToastUtil.toastShort("解析出错")
}
} else {
ToastUtil.toastShort(response.msg)
}
}
}
})
}
override fun getConsultantInfo() {
mModel.getConsultantInfo(mCourseId)
.compose(RxUtils.applySchedulers(mView))
.subscribe(object : CommonObserver<BaseResponse<CourseConsultant>>() {
override fun onError(s: String?) {}
override fun onSuccess(resp: BaseResponse<CourseConsultant>?) {
resp?.let { response ->
if (response.code == 200) {
response.data?.let { info ->
if (1 == info.isOnline) {
mView.goToConsultantInfo(info.doctorHead)
} else {
ToastUtil.toastShort("该咨询师未上线")
}
} ?: let {
ToastUtil.toastShort("解析出错")
}
} else {
ToastUtil.toastShort(response.msg)
}
}
}
})
}
//设置播放列表数据
private fun setPlayList(bean: CourseMediaDetailBean) {
mPlayList.clear()
if (bean.voiceSample.courseMedia.isNotEmpty()) {
for (voiceSampleData in bean.voiceSample.courseMedia) {
voiceSampleData.isDemo = true
mPlayList.add(voiceSampleData)
}
}
if (bean.voiceWhole.courseMedia.isNotEmpty()) {
for (voiceCourseData in bean.voiceWhole.courseMedia) {
mPlayList.add(voiceCourseData)
}
}
}
override fun convertToMusics(): ArrayList<Music> {
val musics = ArrayList<Music>()
mCourPlayBean?.courseExtra?.let { extra ->
mPlayList.forEach { bean ->
musics.add(Music().apply {
path = bean.url
coverPath = extra.pic
title = bean.title
artist = bean.doctorName
canPlay = extra.isBuy || bean.isDemo
})
}
}
return musics
}
override fun addCourseOrder() {
if (mCourseId == 0) {
ToastUtil.toastShort("请退出页面重试")
return
}
val userInfo = findRouteService(IUserService::class.java).getUserInfo()
if (userInfo == null || TextUtils.isEmpty(userInfo.uid)) {
mView.loginByOneKeyLogin()
return
}
mView.commonPayDialog(userInfo, mCourseId.toString())
}
override fun getPlayPosition(): Int = mCurrentPosition
override fun getPlayList(): List<CourseMediaBean> = mPlayList
override fun togglePlay(playPosition: Int) {
if (isCanLoad(playPosition)) {
mView.load()
}
}
override fun isCanLoad(playPosition: Int): Boolean {
ifNotNull(
mCourPlayBean?.courseExtra,
mPlayList.elementAtOrNull(playPosition)
) { extra, bean ->
if (!bean.isDemo && !extra.isBuy) {
if (mView.audioIsPlaying()) mView.audioPausePlay()
mView.buyCourseTipDialog()
} else {
mCurrentPosition = playPosition
mView.setTitle(bean.title)
return true
}
}
return false
}
override fun share() {
mView.shareDialog(mCourPlayBean?.courseExtra?.shareData)
}
override fun showFloatView() {
if (mView.audioIsPlaying() && PlayerFloatHelper.playingType == PlayTypeEnum.PLAY_TYPE_COURSE) {
PlayerFloatHelper.playTempData.clear()
val hashMap = hashMapOf<String, String>()
hashMap["course_id"] = mCourseId.toString()
hashMap["media_type"] = COURSE_AUDIO.toString()
PlayerFloatHelper.playTempData.putAll(hashMap)
}
}
}
\ No newline at end of file
package com.yidianling.course.coursePlay.presenter
import android.content.Intent
import com.ydl.media.audio.model.Music
import com.ydl.ydlcommon.bean.ShareData
import com.ydl.ydlcommon.data.http.BaseResponse
import com.ydl.ydlcommon.mvp.base.IModel
import com.ydl.ydlcommon.mvp.base.IPresenter
import com.ydl.ydlcommon.mvp.base.IView
import com.ydl.ydlcommon.router.YdlUserInfo
import com.yidianling.course.bean.CourseConsultant
import com.yidianling.course.bean.CourseExtraBean
import com.yidianling.course.bean.CourseMediaBean
import com.yidianling.course.bean.CourseMediaDetailBean
import com.yidianling.user.api.bean.UserResponseBean
import io.reactivex.Observable
import java.util.ArrayList
interface IAudioPlayContract {
interface View : IView {
fun showProgressDialog()
fun dismissProgressDialog()
fun updateView(bean: CourseExtraBean, from: Int)
fun setNonWifiTips(show: Boolean)
fun audioIsPlaying(): Boolean
fun audioPausePlay()
fun getAudioMusic(): Music?
fun loginByOneKeyLogin()
fun goToConsultantInfo(url: String)
fun commonPayDialog(userInfo: UserResponseBean.UserInfo, courseId: String)
fun buyCourseTipDialog()
fun shareDialog(share: ShareData?)
fun setTitle(title: String)
fun load()
fun close()
}
interface Presenter : IPresenter<View> {
fun getParams(intent: Intent)
fun getNewIntent(intent: Intent)
fun getCoursePlayData()
fun getConsultantInfo()
fun addCourseOrder()
fun getPlayPosition(): Int
fun getPlayList(): List<CourseMediaBean>
fun togglePlay(playPosition: Int)
fun isCanLoad(playPosition: Int): Boolean
fun share()
fun showFloatView()
fun convertToMusics(): ArrayList<Music>
}
interface Model : IModel {
fun getCoursePlayData(courseId: Int): Observable<BaseResponse<CourseMediaDetailBean>>
fun getConsultantInfo(courseId: Int): Observable<BaseResponse<CourseConsultant>>
}
}
\ No newline at end of file
......@@ -122,9 +122,7 @@ class CoursePlugin : MethodChannel.MethodCallHandler {
if (jumpUrl!!.startsWith("http")) {
val h5Params = H5Params(jumpUrl, "")
NewH5Activity.start(mFragment!!.activity, h5Params)
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(mFragment!!.requireActivity())
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
CourseSendPlugin.sendMsg(false)
return
}
......@@ -164,9 +162,7 @@ class CoursePlugin : MethodChannel.MethodCallHandler {
}
else -> YDLRouterManager.router(jumpUrl)
}
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(mFragment!!.requireActivity())
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
CourseSendPlugin.sendMsg(false)
}
}
......@@ -177,7 +173,7 @@ class CoursePlugin : MethodChannel.MethodCallHandler {
}
PAUSECOURSEPLAY -> {
if (AudioPlayer.get().isPlaying) {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
PlayerFloatHelper.updatePlayState()
}
}
......@@ -194,9 +190,7 @@ class CoursePlugin : MethodChannel.MethodCallHandler {
EventBus.getDefault().post(ScrollStatusChangeEvent(true))
}
IOSPOP -> {
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(mFragment!!.requireActivity())
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
CourseSendPlugin.sendMsg(false)
mFragment!!.activity?.finish()
}
......@@ -204,12 +198,10 @@ class CoursePlugin : MethodChannel.MethodCallHandler {
}
private fun playMedia(fileInfo: Map<*, *>) {
if (PlayerFloatHelper.isShow(mFragment!!.requireActivity())) {
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(mFragment!!.requireActivity())
AudioPlayer.get().stopPlayer()
if (PlayerFloatHelper.isShow()) {
PlayerFloatHelper.removeResetView()
} else {
PlayerFloatHelper.removeView(mFragment!!.requireActivity())
PlayerFloatHelper.removeView()
}
......@@ -245,10 +237,8 @@ class CoursePlugin : MethodChannel.MethodCallHandler {
)
checkPermission()
if (PlayerFloatHelper.isShow(mFragment!!.requireActivity())) {
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(mFragment!!.requireActivity())
AudioPlayer.get().stopPlayer()
if (PlayerFloatHelper.isShow()) {
PlayerFloatHelper.removeResetView()
}
}
......@@ -262,8 +252,7 @@ class CoursePlugin : MethodChannel.MethodCallHandler {
}
override fun onPlayFinish() {
PlayerFloatHelper.hide()
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
CourseSendPlugin.sendMsg(false)
}
......
......@@ -22,9 +22,7 @@ class CoursePlayLifecycle : Application.ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity?) {
if (!PlayerFloatHelper.isCanClick) {
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(activity!!)
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
} else {
PlayerFloatHelper.showIfPlaying(activity!!)
if (!TextUtils.isEmpty(PlayerFloatHelper.playTempData["course_id"])) {
......
......@@ -5,17 +5,18 @@ import android.app.Application
import android.content.Context
import android.content.Intent
import com.alibaba.android.arouter.facade.annotation.Route
import com.ydl.course.api.ICourseService
import com.ydl.media.audio.AudioPlayer
import com.ydl.media.view.PlayerFloatHelper
import com.ydl.course.api.ICourseService
import com.yidianling.course.bean.COURSE_AUDIO
import com.yidianling.course.courseNew.CourseTopicActivity
import com.yidianling.course.courseNew.mine.MyCourseActivity
import com.yidianling.course.coursePlay.AudioPlayActivity
import com.yidianling.course.coursePlay.CourseCommentActivity
import com.yidianling.course.coursePlay.CoursePlayActivity
import com.yidianling.course.coursePlay.CourseReplyListActivity
import com.yidianling.course.flutterPlugin.CourseSendPlugin
import com.yidianling.course.lifeCallback.CoursePlayLifecycle
import java.util.*
/**
* @author jiucheng
......@@ -32,7 +33,7 @@ class CourseServiceImp : ICourseService {
override fun closePlayer() {
if (AudioPlayer.get().isPlaying){
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
}
......@@ -49,7 +50,7 @@ class CourseServiceImp : ICourseService {
}
override fun pause() {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
override fun sendLoginStatusToFlutter() {
......@@ -61,7 +62,11 @@ class CourseServiceImp : ICourseService {
}
override fun startCoursePlayActivity(activity: Activity, courseId: Int, coursePlayUrl: String?, from: Int, mediaType: String?) {
val intent = Intent(activity, CoursePlayActivity::class.java)
val intent = if (COURSE_AUDIO.toString() == mediaType) {
Intent(activity, AudioPlayActivity::class.java)
} else {
Intent(activity, CoursePlayActivity::class.java)
}
intent.putExtra("course_id", courseId)
intent.putExtra("coursePlayUrl", coursePlayUrl)
intent.putExtra("from", from)
......
......@@ -100,6 +100,13 @@ public class CourseRetrofitUtils {
return YDLHttpUtils.Companion.obtainApi(NetApiStore.class).getCoursePlayData(courseId);
}
/**
* 获取咨询师上线状态
*/
public static Observable<BaseResponse<CourseConsultant>> getConsultantInfo(String courseId) {
return YDLHttpUtils.Companion.obtainApi(NetApiStore.class).getConsultantInfo(courseId);
}
public static Observable<BaseAPIResponse<CourseSpecialListBean>> specialListRequest(CourseSpecialListParam param) {
List<FormatText> list = NetworkParamsUtils.getPostList(param);
return YDLHttpUtils.Companion.obtainApi(NetApiStore.class).courseSpecialList(NetworkParamsUtils.getMaps(list));
......
......@@ -5,6 +5,7 @@ import com.ydl.ydlcommon.data.http.BaseResponse;
import com.yidianling.course.bean.CouponHintResponseBean;
import com.yidianling.course.bean.Course;
import com.yidianling.course.bean.CourseAddOrderBean;
import com.yidianling.course.bean.CourseConsultant;
import com.yidianling.course.bean.CourseCouponBean;
import com.yidianling.course.bean.CourseListDataBean;
import com.yidianling.course.bean.CourseMediaDetailBean;
......@@ -131,6 +132,11 @@ public interface NetApiStore {
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA)
Observable<BaseResponse<CourseMediaDetailBean>> getCoursePlayData(@Query("courseId") String courseId);
//获取咨询师上线状态
@GET("auth/course/getCourseDoctorInfo")
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA)
Observable<BaseResponse<CourseConsultant>> getConsultantInfo(@Query("courseId") String courseId);
//课程专题列表
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA)
......
package com.yidianling.course.widget
import android.annotation.SuppressLint
import android.content.Context
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.Nullable
import androidx.annotation.RawRes
import com.bumptech.glide.Glide
import com.ydl.media.audio.AudioPlayer
import com.ydl.media.audio.OnPlayerEventListener
import com.ydl.media.audio.enums.PlayModeEnum
import com.ydl.media.audio.model.Music
import com.ydl.media.view.PlayTypeEnum
import com.ydl.media.view.PlayerFloatHelper
import com.yidianling.common.tools.RxNetTool
import com.yidianling.course.R
/**
* 音频播放UI
*/
class AudioPlayView(context: Context, attrs: AttributeSet?) :
FrameLayout(context, attrs), OnPlayerEventListener {
private var mSeekBarIsTouch = false
private var mProgress = 0
var mNonWifiTips = true
var mListener: ((playPosition: Int) -> Boolean)? = null
private var iv_bg: ImageView
private var iv_rewind: ImageView
private var iv_fast_forward: ImageView
private var iv_pre: ImageView
private var iv_next: ImageView
private var iv_play_status: ImageView
private var seekbar: SeekBar
private var tv_progress: TextView
private var tv_duration: TextView
init {
AudioPlayer.get().addOnPlayEventListener(this)
View.inflate(context, R.layout.audio_play_view, this)
iv_bg = findViewById(R.id.iv_bg)
iv_rewind = findViewById(R.id.iv_rewind)
iv_fast_forward = findViewById(R.id.iv_fast_forward)
iv_pre = findViewById(R.id.iv_pre)
iv_next = findViewById(R.id.iv_next)
seekbar = findViewById(R.id.seekbar)
tv_progress = findViewById(R.id.tv_progress)
tv_duration = findViewById(R.id.tv_duration)
iv_play_status = findViewById(R.id.iv_play_status)
iv_bg.setOnClickListener {
if (AudioPlayer.get().isPlaying)
AudioPlayer.get().pausePlayer()
else if (AudioPlayer.get().isPausing)
AudioPlayer.get().startPlayer()
}
iv_rewind.setOnClickListener {
AudioPlayer.get().seekTo(position = AudioPlayer.get().currentPosition.minus(15000))
}
iv_fast_forward.setOnClickListener {
AudioPlayer.get().seekTo(position = AudioPlayer.get().currentPosition.plus(15000))
}
iv_pre.setOnClickListener {
AudioPlayer.get().prev()
}
iv_next.setOnClickListener {
AudioPlayer.get().next()
}
seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
tv_progress.text = getStringTime(progress)
mProgress = progress
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
mSeekBarIsTouch = true
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
mSeekBarIsTouch = false
//拖动seekbar时不进行以下操作
if (!mSeekBarIsTouch) {
AudioPlayer.get().seekTo(-1, mProgress.toLong())
}
}
})
}
fun setData(playPosition: Int, playList: ArrayList<Music>, from: Int) {
if (playList.isEmpty()) return
AudioPlayer.get().playMode = PlayModeEnum.LIST
AudioPlayer.get().addPlayList(playList)
if (isPlaying()) mNonWifiTips = false
if (RxNetTool.isWifi(context)) {
loadAudio(from, playPosition, playList)
} else {
if (mNonWifiTips) {
showNonWifiTips {
mNonWifiTips = false
loadAudio(from, playPosition, playList)
}
} else {
loadAudio(from, playPosition, playList)
}
}
}
private fun showNonWifiTips(block: () -> Unit) {
block.invoke()
}
private fun loadAudio(from: Int, playPosition: Int, playList: ArrayList<Music>) {
if (isPlaying() && (from == 1 || from == 2)) {
if (TextUtils.equals(getCurrentUrl(), playList[playPosition].path)) {
seekbar.max = AudioPlayer.get().getDuration().toInt()
tv_duration.text = getStringTime(seekbar.max)
displayPlayStatus(R.drawable.course_pause)
} else {
load(playPosition)
}
} else {
load(playPosition)
}
}
/**
* 获取当前播放url
*/
private fun getCurrentUrl(): String {
return AudioPlayer.get().playMusic?.path.orEmpty()
}
fun load(index: Int) {
PlayerFloatHelper.playingType = PlayTypeEnum.PLAY_TYPE_COURSE
AudioPlayer.get().load(index)
}
override fun onPreLoad(playPosition: Int): Boolean {
return mListener?.invoke(playPosition) ?: true
}
@SuppressLint("SetTextI18n")
override fun onLoad(music: Music) {
displayPlayStatus(R.drawable.course_loading5, true)
seekbar.progress = 0
tv_progress.text = "00:00"
}
override fun onStartPlay() {
postDelayed({
displayPlayStatus(R.drawable.course_pause)
}, 200) // 为了列表切换结束后还能看到loading效果
}
override fun onPrepared(duration: Long) {
seekbar.max = duration.toInt()
tv_duration.text = getStringTime(seekbar.max)
}
override fun onBufferingUpdate(percent: Int) {
seekbar.secondaryProgress = percent * seekbar.max / 100
}
override fun onPublish(percent: Int, currentPosition: Long) {
if (!mSeekBarIsTouch) {
val currentProgress = currentPosition.toInt()
seekbar.progress =
if (seekbar.max - currentProgress < 1000) seekbar.max else currentProgress
tv_progress.text = getStringTime(seekbar.progress)
}
}
override fun onComplete() {
tv_progress.text = getStringTime(seekbar.max)
}
override fun onPausePlay() {
displayPlayStatus(R.drawable.course_play)
}
private fun displayPlayStatus(
@RawRes @DrawableRes @Nullable resourceId: Int,
isGif: Boolean = false
) {
context?.let {
if (isGif) {
Glide.with(it).asGif().load(resourceId).into(iv_play_status)
} else {
Glide.with(it).asBitmap().load(resourceId).into(iv_play_status)
}
}
}
private fun getStringTime(time: Int): String {
if (time <= 0) return "00:00"
val min = time / 60000
val ss = (time - min * 60000) / 1000
val m = "" + min
val s = "" + ss
return String.format("%02d:%02d", m, s)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
AudioPlayer.get().removeOnPlayEventListener(this)
}
fun setSpeed(speed: Float) = AudioPlayer.get().setSpeed(speed)
fun getSpeed() = AudioPlayer.get().getSpeed()
fun isPlaying() = AudioPlayer.get().isPlaying
fun pausePlay() = AudioPlayer.get().pausePlayer()
fun getAudioMusic() = AudioPlayer.get().playMusic
}
\ No newline at end of file
......@@ -273,12 +273,10 @@ class CourseItemNewView : ConstraintLayout {
private fun playMedia() {
if (PlayerFloatHelper.isShow(mContext)) {
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(mContext)
AudioPlayer.get().stopPlayer()
if (PlayerFloatHelper.isShow()) {
PlayerFloatHelper.removeResetView()
} else {
PlayerFloatHelper.removeView(mContext)
PlayerFloatHelper.removeView()
}
val music = Music()
......@@ -310,10 +308,8 @@ class CourseItemNewView : ConstraintLayout {
)
checkPermission()
if (PlayerFloatHelper.isShow(mContext)) {
PlayerFloatHelper.hide()
PlayerFloatHelper.removeView(mContext)
AudioPlayer.get().stopPlayer()
if (PlayerFloatHelper.isShow()) {
PlayerFloatHelper.removeResetView()
}
}
......@@ -324,8 +320,7 @@ class CourseItemNewView : ConstraintLayout {
}
override fun onPlayFinish() {
PlayerFloatHelper.hide()
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
}
override fun onPauseClick() {
......
package com.yidianling.course.widget
import android.content.Context
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.ydl.media.audio.utils.PlayProgressUtil
import com.ydl.ydlcommon.base.BaseDialogFragment
import com.ydl.ydlcommon.utils.extend.gone
import com.ydl.ydlcommon.utils.extend.visible
import com.yidianling.common.view.ui.VoicePlayingIcon
import com.yidianling.course.R
import com.yidianling.course.bean.CourseMediaBean
import com.yidianling.course.coursePlay.presenter.IAudioPlayContract
/**
* 课程目录弹窗
*/
class CourseListDialog : BaseDialogFragment() {
var mView: IAudioPlayContract.View? = null
var mPresenter: IAudioPlayContract.Presenter? = null
var mCurrentPosition = 0
private var mAdapter: CourseListDialog.CourseListAdapter? = null
private val mCourseList = mutableListOf<CourseMediaBean>()
private var mIsBuy = false
private lateinit var recycler_list: RecyclerView
override fun getLayoutResourceId(): Int = R.layout.dialog_course_list
override fun getGravity(): Int = Gravity.BOTTOM
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
context?.let { ctx ->
recycler_list = view.findViewById(R.id.recycler_list)
recycler_list.adapter = mAdapter ?: CourseListAdapter(ctx).also { mAdapter = it }
}
}
fun setData(isBuy: Boolean, currentPosition: Int, courseList: List<CourseMediaBean>) {
mIsBuy = isBuy
mCurrentPosition = currentPosition
mCourseList.clear()
mCourseList.addAll(courseList)
// mAdapter?.notifyDataSetChanged()
}
fun setCurrentPosition(currentPosition: Int) {
mCurrentPosition = currentPosition
// mAdapter?.notifyDataSetChanged()
}
inner class CourseListAdapter(
context: Context,
private val inflater: LayoutInflater = LayoutInflater.from(context)
) : RecyclerView.Adapter<CourseListAdapter.CourseListVH>() {
inner class CourseListVH(view: View) : RecyclerView.ViewHolder(view) {
var voice_play: VoicePlayingIcon
var iv_play: ImageView
var iv_audition: ImageView
var tv_title: TextView
init {
voice_play = view.findViewById(R.id.voice_play)
iv_play = view.findViewById(R.id.iv_play)
iv_audition = view.findViewById(R.id.iv_audition)
tv_title = view.findViewById(R.id.tv_title)
}
}
override fun onCreateViewHolder(p0: ViewGroup, itemType: Int): CourseListVH =
CourseListVH(inflater.inflate(R.layout.item_course_playlist, p0, false))
override fun onBindViewHolder(vh: CourseListVH, position: Int) {
mCourseList.elementAtOrNull(position)?.let { bean ->
vh.run {
if (mCurrentPosition == position) {
voice_play.visible()
voice_play.start()
iv_play.gone()
} else {
voice_play.gone()
voice_play.stop()
iv_play.visible()
if (mIsBuy) {
iv_play.setImageResource(R.drawable.course_play_orange)
} else {
if (bean.isDemo) {
iv_play.setImageResource(R.drawable.course_play_orange)
} else {
iv_play.setImageResource(R.drawable.course_play_lock)
}
}
}
iv_audition.visibility = if (bean.isDemo) View.VISIBLE else View.GONE // 试听标签
tv_title.text = bean.title
itemView.setOnClickListener {
if (mCurrentPosition == position) {
dismissAllowingStateLoss()
return@setOnClickListener
}
if (!bean.isDemo && !mIsBuy) {
mView?.buyCourseTipDialog()
} else {
PlayProgressUtil.saveProgress(
context,
mCourseList.elementAtOrNull(position)?.url,
0
)
mCurrentPosition = position
notifyDataSetChanged()
mPresenter?.togglePlay(position)
dismissAllowingStateLoss()
}
}
}
}
}
override fun getItemCount(): Int = mCourseList.size
}
}
package com.yidianling.course.widget
import android.content.Context
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.ydl.ydlcommon.base.BaseDialogFragment
import com.yidianling.course.R
/**
* 课程倍速dialog
*/
class CourseSpeedDialog : BaseDialogFragment() {
private val mSpeedList = floatArrayOf(0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f, 3.0f)
var mListener: ((speed: Float) -> Unit)? = null
private var mAdapter: CourseSpeedAdapter? = null
private var mOldPosition = -1
private var mCurrentPosition = 2
private lateinit var recycler_list: RecyclerView
private lateinit var tv_cancel: TextView
override fun getLayoutResourceId(): Int = R.layout.dialog_course_speed
override fun getGravity(): Int = Gravity.BOTTOM
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
context?.let {
recycler_list = view.findViewById(R.id.recycler_list)
tv_cancel = view.findViewById(R.id.tv_cancel)
if (null == mAdapter) mAdapter = CourseSpeedAdapter(it)
recycler_list.adapter = mAdapter
tv_cancel.setOnClickListener {
dismissAllowingStateLoss()
}
}
}
fun setSpeedPosition(speed: Float) {
mCurrentPosition = mSpeedList.indexOfFirst { speed == it }
}
inner class CourseSpeedAdapter(
context: Context,
private val inflater: LayoutInflater = LayoutInflater.from(context)
) : RecyclerView.Adapter<CourseSpeedAdapter.CourseSpeedVH>() {
inner class CourseSpeedVH(view: View) : RecyclerView.ViewHolder(view) {
var tv_speed: TextView
init {
tv_speed = view.findViewById(R.id.tv_speed)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, itemType: Int): CourseSpeedVH =
CourseSpeedVH(inflater.inflate(R.layout.item_course_speed, viewGroup, false))
override fun onBindViewHolder(vh: CourseSpeedVH, position: Int) {
vh.let {
mSpeedList.elementAtOrNull(position)?.let { speed ->
it.tv_speed.run {
setTextColor(resources.getColor(if (position == mCurrentPosition) R.color.color_1DA1F2 else R.color.color_1C1F28))
text = speed.toString()
setOnClickListener {
if (position == mCurrentPosition) {
dismissAllowingStateLoss()
return@setOnClickListener
}
mOldPosition = mCurrentPosition
mCurrentPosition = position
notifyItemChanged(mOldPosition)
notifyItemChanged(mCurrentPosition)
mListener?.invoke(speed)
dismissAllowingStateLoss()
}
}
}
}
}
override fun getItemCount(): Int = mSpeedList.size
}
}
\ No newline at end of file
......@@ -47,11 +47,11 @@ class HPlayView : RelativeLayout, OnPlayerEventListener {
AudioPlayer.get().addOnPlayEventListener(this)
course_audio_play_icon.setOnClickListener {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
img_gif.setOnClickListener {
if (!AudioPlayer.get().isPlaying) {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
}
......@@ -117,7 +117,7 @@ class HPlayView : RelativeLayout, OnPlayerEventListener {
fun play(index: Int) {
PlayerFloatHelper.playingType = PlayTypeEnum.PLAY_TYPE_COURSE
AudioPlayer.get().play(index)
AudioPlayer.get().load(index)
updateButton()
}
......@@ -138,9 +138,10 @@ class HPlayView : RelativeLayout, OnPlayerEventListener {
}
}
override fun onPreLoad(playPosition: Int): Boolean = true
@SuppressLint("SetTextI18n")
override fun onChange(music: Music) {
override fun onLoad(music: Music) {
if (mContext != null) {
displayImage(com.yidianling.course.R.drawable.course_loading5,img_gif,true)
}
......@@ -150,11 +151,11 @@ class HPlayView : RelativeLayout, OnPlayerEventListener {
text_start_time.text = "00:00"
}
override fun onPlayerStart() {
override fun onStartPlay() {
setGifVisibity(true)
}
override fun onPlayerPause() {
override fun onPausePlay() {
if (AudioPlayer.get().isPlaying) {
setGifVisibity(true)
} else {
......
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:topLeftRadius="8dp"
android:topRightRadius="8dp"/>
<solid android:color="@android:color/white"/>
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0,0h24v24h-24z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M14.2,5l-7.2,7.1111l7.2,7.1111"
android:strokeLineJoin="round"
android:strokeWidth="1.8"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="35dp"
android:height="35dp"
android:viewportWidth="35"
android:viewportHeight="35">
<path
android:pathData="M0.5,0.5h34v34h-34z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M7.9527,17.7392C8.0227,17.2311 8.5559,15.0407 8.8424,14.2479C10.2325,10.4007 13.8216,7.8959 17.7361,7.895C19.5587,7.895 21.2717,8.4306 22.7243,9.3859L21.8473,9.3859C21.0936,9.3859 20.4769,10.0373 20.4769,10.8334C20.4769,11.6295 21.0936,12.2808 21.8473,12.2808L25.9584,12.2808C26.7121,12.2808 27.3288,11.6295 27.3288,10.8334L27.3288,6.491C27.3288,5.6963 26.7121,5.045 25.9584,5.045C25.2047,5.045 24.588,5.6963 24.588,6.4924L24.588,7.2017C22.5627,5.7633 20.1767,4.9967 17.7361,5C14.7624,5 12.0354,6.1147 9.9127,7.9819L9.899,7.9819C9.5975,8.2569 9.296,8.5464 9.0082,8.8503C8.7204,9.1398 8.4738,9.4293 8.2271,9.7478C8.186,9.8057 8.1449,9.8491 8.1038,9.907C7.9941,10.0662 7.8845,10.211 7.7886,10.3557C7.6652,10.5294 7.5419,10.7176 7.4323,10.8913C7.3089,11.0794 7.1993,11.2676 7.0897,11.4558C7.0075,11.6439 6.9115,11.8176 6.8156,12.0058C6.706,12.2084 6.61,12.4256 6.5141,12.6268C6.4456,12.8005 6.3771,12.9886 6.3086,13.1623C6.1578,13.5531 6.0208,13.944 5.9112,14.3492C5.8015,14.7545 5.2381,16.5044 5.2381,17.9926C5.3407,18.6628 5.8122,18.9979 6.6529,18.9979C7.5775,18.9979 7.798,18.864 7.9527,17.7392ZM28.6443,17.8422L28.78,17.8506C29.4827,17.9327 30,18.3696 30,19.3126C29.9998,22.3763 26.9954,28.6181 22.7275,29.9085C21.9104,30.168 21.353,29.8757 21.0553,29.0315C20.7577,28.1874 21.021,27.5571 21.8454,27.1406C25.2123,24.9129 26.2524,23.9403 27.2726,19.1403C27.2726,18.7152 27.4881,18.4055 27.7714,18.1634C28.0546,17.9213 28.4179,17.8092 28.78,17.8506ZM19.9882,19.9111L19.9882,21.3425L17.2499,21.3425L17.2499,22.4064C17.3204,22.3548 17.4389,22.2968 17.6054,22.2323C17.772,22.1678 17.9609,22.1356 18.1723,22.1356C18.4733,22.1356 18.7343,22.1807 18.9553,22.271C19.1763,22.3613 19.3573,22.4773 19.4982,22.6192C19.6391,22.7611 19.7512,22.9045 19.8344,23.0496C19.9177,23.1947 19.9818,23.3446 20.0266,23.4993C20.0714,23.6541 20.1003,23.8105 20.1131,23.9684C20.1259,24.1264 20.1323,24.286 20.1323,24.4472C20.1323,24.6535 20.1227,24.8405 20.1035,25.0082C20.0842,25.1758 20.0506,25.3322 20.0026,25.4773C19.9545,25.6223 19.8905,25.7577 19.8104,25.8835C19.7304,26.0092 19.6263,26.1366 19.4982,26.2655C19.3508,26.4138 19.1459,26.5492 18.8833,26.6717C18.6207,26.7942 18.2972,26.8555 17.9129,26.8555C17.5286,26.8555 17.1987,26.7926 16.9233,26.6669C16.6479,26.5412 16.4365,26.4041 16.2892,26.2558C16.0458,26.0108 15.8904,25.7529 15.8232,25.4821C15.7559,25.2113 15.7127,24.9501 15.6935,24.6987L15.6935,24.6987L17.2692,24.6987C17.2948,24.9243 17.3572,25.1017 17.4565,25.2306C17.5558,25.3596 17.7079,25.4241 17.9129,25.4241C18.0922,25.4241 18.2395,25.366 18.3548,25.25C18.4893,25.1146 18.5566,24.847 18.5566,24.4472C18.5566,24.0668 18.4974,23.8088 18.3789,23.6734C18.2604,23.538 18.105,23.4703 17.9129,23.4703C17.7976,23.4703 17.7031,23.4881 17.6294,23.5235C17.5558,23.559 17.4949,23.6009 17.4469,23.6493C17.3989,23.6976 17.362,23.7508 17.3364,23.8088C17.3108,23.8669 17.2884,23.9185 17.2692,23.9636L17.2692,23.9636L15.8184,23.9636L15.8184,19.9111L19.9882,19.9111ZM13.7719,19.9111L13.7719,26.7975L12.1962,26.7975L12.1962,21.6036L10.7359,22.8803L10.7359,21.1878L12.1962,19.9111L13.7719,19.9111ZM7.94,21.7294L7.94,23.1995L9.4004,23.1995L9.4004,24.689L7.94,24.689L7.94,26.1591L6.4604,26.1591L6.4604,24.689L5,24.689L5,23.1995L6.4604,23.1995L6.4604,21.7294L7.94,21.7294Z"
android:strokeAlpha="0.9049247"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"
android:fillAlpha="0.9049247"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="25dp"
android:viewportWidth="25"
android:viewportHeight="25">
<path
android:pathData="M0.5,0.5h24v24h-24z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M19.5,5.5L19.5,19.5"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M16.9844,12.109C17.0845,12.676 16.9682,13.2813 16.6119,13.7903C16.4613,14.0055 16.2741,14.1926 16.0589,14.3433L16.0589,14.3433L7.7168,20.1827C7.434,20.3807 7.0978,20.4453 6.7828,20.3897C6.4677,20.3341 6.1739,20.1583 5.976,19.8755C5.8289,19.6654 5.75,19.4152 5.75,19.1587L5.75,19.1587L5.75,5.8413C5.75,5.4961 5.8899,5.1836 6.1161,4.9574C6.3423,4.7312 6.6548,4.5913 7,4.5913C7.2565,4.5913 7.5067,4.6702 7.7168,4.8173L7.7168,4.8173L16.0589,10.6567C16.5679,11.013 16.8843,11.5419 16.9844,12.109Z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:pathData="M0,0h30v30h-30z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M10.9611,22.8393C10.9611,24.0338 10.074,25 8.9805,25C7.8871,25 7,24.0338 7,22.8393L7,7.1607C7,5.9662 7.8871,5 8.9805,5C10.074,5 10.9611,5.9662 10.9611,7.1607L10.9611,22.8393ZM23,22.8393C23,24.0338 22.1129,25 21.0195,25C19.926,25 19.0389,24.0338 19.0389,22.8393L19.0389,7.1607C19.0389,5.9662 19.926,5 21.0195,5C22.1129,5 23,5.9662 23,7.1607L23,22.8393Z"
android:strokeWidth="1"
android:fillColor="#1C1F28"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:pathData="M0,0h30v30h-30z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M22.5558,15.1804C22.6559,15.7475 22.5396,16.3527 22.1833,16.8617C22.0327,17.0769 21.8455,17.2641 21.6303,17.4147L21.6303,17.4147L11.1454,24.7542C10.8626,24.9521 10.5264,25.0167 10.2113,24.9611C9.8963,24.9055 9.6025,24.7297 9.4045,24.4469C9.2575,24.2368 9.1786,23.9866 9.1786,23.7301L9.1786,23.7301L9.1786,7.4127C9.1786,7.0676 9.3185,6.7551 9.5447,6.5289C9.7709,6.3027 10.0834,6.1627 10.4286,6.1627C10.685,6.1627 10.9353,6.2416 11.1454,6.3887L11.1454,6.3887L21.6303,13.7282C22.1393,14.0845 22.4557,14.6133 22.5558,15.1804Z"
android:strokeWidth="1.5"
android:fillColor="#1C1F28"
android:strokeColor="#1C1F28"
android:fillType="evenOdd"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="25dp"
android:viewportWidth="25"
android:viewportHeight="25">
<path
android:pathData="M0.5,0.5h24v24h-24z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M5.5,5.5L5.5,19.5"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M8.0156,12.109C7.9155,12.676 8.0318,13.2813 8.3881,13.7903C8.5387,14.0055 8.7259,14.1926 8.9411,14.3433L8.9411,14.3433L17.2832,20.1827C17.566,20.3807 17.9022,20.4453 18.2172,20.3897C18.5323,20.3341 18.8261,20.1583 19.024,19.8755C19.1711,19.6654 19.25,19.4152 19.25,19.1587L19.25,19.1587L19.25,5.8413C19.25,5.4961 19.1101,5.1836 18.8839,4.9574C18.6577,4.7312 18.3452,4.5913 18,4.5913C17.7435,4.5913 17.4933,4.6702 17.2832,4.8173L17.2832,4.8173L8.9411,10.6567C8.4321,11.013 8.1157,11.5419 8.0156,12.109Z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="35dp"
android:height="35dp"
android:viewportWidth="35"
android:viewportHeight="35">
<path
android:pathData="M0.5,0.5h34v34h-34z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M27.1712,17.7392C27.1008,17.2311 26.5646,15.0407 26.2765,14.2479C24.8785,10.4007 21.2694,7.8959 17.3328,7.895C15.4999,7.895 13.7773,8.4306 12.3166,9.3859L13.1985,9.3859C13.9565,9.3859 14.5766,10.0373 14.5766,10.8334C14.5766,11.6295 13.9565,12.2808 13.1985,12.2808L9.0643,12.2808C8.3064,12.2808 7.6862,11.6295 7.6862,10.8334L7.6862,6.491C7.6862,5.6963 8.3064,5.045 9.0643,5.045C9.8223,5.045 10.4424,5.6963 10.4424,6.4924L10.4424,7.2017C12.4791,5.7633 14.8785,4.9967 17.3328,5C20.3232,5 23.0656,6.1147 25.2002,7.9819L25.214,7.9819C25.5172,8.2569 25.8204,8.5464 26.1098,8.8503C26.3992,9.1398 26.6472,9.4293 26.8953,9.7478C26.9366,9.8057 26.9779,9.8491 27.0193,9.907C27.1295,10.0662 27.2398,10.211 27.3362,10.3557C27.4603,10.5294 27.5843,10.7176 27.6945,10.8913C27.8186,11.0794 27.9288,11.2676 28.0391,11.4558C28.1218,11.6439 28.2182,11.8176 28.3147,12.0058C28.4249,12.2084 28.5214,12.4256 28.6179,12.6268C28.6868,12.8005 28.7557,12.9886 28.8246,13.1623C28.9762,13.5531 29.114,13.944 29.2242,14.3492C29.3345,14.7545 29.9011,16.5044 29.9011,17.9926C29.7979,18.6628 29.3237,18.9979 28.4783,18.9979C27.5485,18.9979 27.3268,18.864 27.1712,17.7392ZM12.3134,29.9085C8.0215,28.6181 5.0002,22.3763 5,19.3126C5,18.3696 5.5202,17.9327 6.2268,17.8506C6.591,17.8092 6.9563,17.9213 7.2412,18.1634C7.526,18.4055 7.7427,18.7152 7.7427,19.1403C8.7687,23.9403 9.8146,24.9129 13.2004,27.1406C14.0294,27.5571 14.2943,28.1874 13.9949,29.0315C13.6956,29.8757 13.1351,30.168 12.3134,29.9085Z"
android:strokeAlpha="0.9007859"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"
android:fillAlpha="0.9007859"/>
<path
android:pathData="M19.3062,26.4446L19.3062,24.9551L16.1661,24.9551L16.1661,26.4446L19.3062,26.4446ZM23.6622,28.5531L23.6622,21.6667L22.0922,21.6667L20.637,22.9434L20.637,24.636L22.0922,23.3593L22.0922,28.5531L23.6622,28.5531ZM27.7885,28.6111C28.1714,28.6111 28.4937,28.5499 28.7554,28.4273C29.0171,28.3048 29.2213,28.1694 29.3681,28.0211C29.4958,27.8922 29.5995,27.7648 29.6793,27.6391C29.7591,27.5133 29.8229,27.3779 29.8708,27.2329C29.9186,27.0878 29.9521,26.9314 29.9713,26.7638C29.9904,26.5961 30,26.4091 30,26.2028C30,26.0416 29.9936,25.882 29.9809,25.724C29.9681,25.5661 29.9394,25.4097 29.8947,25.255C29.85,25.1002 29.7862,24.9503 29.7032,24.8052C29.6202,24.6601 29.5086,24.5167 29.3681,24.3748C29.2277,24.233 29.0474,24.1169 28.8272,24.0266C28.607,23.9363 28.347,23.8912 28.047,23.8912C27.8364,23.8912 27.6481,23.9235 27.4821,23.9879C27.3162,24.0524 27.1981,24.1104 27.1279,24.162L27.1279,23.0981L29.8564,23.0981L29.8564,21.6667L25.7014,21.6667L25.7014,25.7192L27.1471,25.7192C27.1662,25.6741 27.1885,25.6225 27.2141,25.5645C27.2396,25.5064 27.2763,25.4532 27.3242,25.4049C27.372,25.3565 27.4327,25.3146 27.5061,25.2791C27.5795,25.2437 27.6736,25.2259 27.7885,25.2259C27.98,25.2259 28.1347,25.2936 28.2528,25.429C28.3709,25.5645 28.4299,25.8224 28.4299,26.2028C28.4299,26.6026 28.3629,26.8702 28.2289,27.0056C28.114,27.1216 27.9672,27.1797 27.7885,27.1797C27.5842,27.1797 27.4327,27.1152 27.3337,26.9862C27.2348,26.8573 27.1726,26.68 27.1471,26.4543L25.577,26.4543C25.5961,26.7057 25.6392,26.9669 25.7062,27.2377C25.7732,27.5085 25.928,27.7664 26.1705,28.0115C26.3173,28.1598 26.528,28.2968 26.8024,28.4225C27.0768,28.5482 27.4055,28.6111 27.7885,28.6111Z"
android:strokeAlpha="0.9007859"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"
android:fillAlpha="0.9007859"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="3dp" />
<solid android:color="#9AFAFAFF" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="3dp" />
<solid android:color="#9AFAFAFF" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="3dp" />
<solid android:color="@color/color_1DA1F2" />
</shape>
</clip>
</item>
</layer-list>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="17dp"
android:height="17dp" />
<stroke android:color="@color/white" android:width="6dp"/>
<solid android:color="@color/color_1DA1F2" />
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M10.7744,4C6.94,4.5762 4,7.8845 4,11.8796C4,16.2804 7.5676,19.848 11.9684,19.848C15.9614,19.848 19.2685,16.9109 19.8471,13.0793"
android:strokeLineJoin="round"
android:strokeWidth="1.8"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M11.6696,12.1744L18.642,5.202"
android:strokeLineJoin="round"
android:strokeWidth="1.8"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M15.6538,4.206L19.14,4.206C19.415,4.206 19.638,4.4289 19.638,4.704L19.638,8.1946"
android:strokeLineJoin="round"
android:strokeWidth="1.8"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M0,0h12v12h-12z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M6,6m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#979797"
android:fillType="evenOdd"/>
<path
android:pathData="M5.9724,3.5028L5.9724,6.1028"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#979797"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M6.0724,6.1028L8.6724,6.1028"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#979797"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0,0h24v24h-24z"
android:strokeAlpha="0"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0"/>
<path
android:pathData="M12,14m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#242424"
android:fillType="evenOdd"/>
<path
android:pathData="M17,9C17,6.2386 14.7614,4 12,4C9.2386,4 7,6.2386 7,9"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#242424"
android:fillType="evenOdd"/>
<path
android:pathData="M7.75,8.75L16.25,8.75A3,3 0,0 1,19.25 11.75L19.25,16.25A3,3 0,0 1,16.25 19.25L7.75,19.25A3,3 0,0 1,4.75 16.25L4.75,11.75A3,3 0,0 1,7.75 8.75z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#242424"
android:fillType="evenOdd"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cl_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="#9F998F">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#C8000000"/>
<ImageView
android:id="@+id/iv_back"
android:layout_width="36dp"
android:layout_height="36dp"
android:paddingStart="12dp"
android:paddingTop="12dp"
android:src="@drawable/course_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_share"
android:layout_width="36dp"
android:layout_height="0dp"
android:paddingEnd="12dp"
android:src="@drawable/course_share"
app:layout_constraintEnd_toEndOf="parent"
android:paddingTop="12dp"
app:layout_constraintTop_toTopOf="@id/iv_back"
app:layout_constraintBottom_toBottomOf="@id/iv_back"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_back">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_pic"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="34dp"
android:layout_marginTop="28dp"
android:layout_marginEnd="34dp"
android:scaleType="fitXY"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/picture_album_bg" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:textStyle="bold"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_pic"
tools:text="关注孩子心理健康,18天带领孩子1111111111" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="33dp"
android:layout_marginTop="15dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title"
app:roundPercent="1"
tools:src="@drawable/picture_album_bg" />
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:textColor="@android:color/white"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@id/iv_icon"
app:layout_constraintStart_toEndOf="@id/iv_icon"
app:layout_constraintTop_toTopOf="@id/iv_icon"
tools:text="你好啊" />
<ImageView
android:id="@+id/iv_detail"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="56dp"
android:src="@drawable/course_detail"
app:layout_constraintEnd_toStartOf="@id/iv_speed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_name" />
<ImageView
android:id="@+id/iv_speed"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/course_speed"
app:layout_constraintEnd_toStartOf="@id/iv_list"
app:layout_constraintStart_toEndOf="@id/iv_detail"
app:layout_constraintTop_toTopOf="@id/iv_detail" />
<ImageView
android:id="@+id/iv_list"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/course_list"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_speed"
app:layout_constraintTop_toTopOf="@id/iv_detail" />
<TextView
android:id="@+id/tv_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="7dp"
android:text="详情"
android:textColor="@android:color/white"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/tv_speed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_detail" />
<TextView
android:id="@+id/tv_speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="7dp"
tools:text="1.0X"
android:textColor="@android:color/white"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/tv_list"
app:layout_constraintStart_toEndOf="@id/tv_detail"
app:layout_constraintTop_toTopOf="@id/tv_detail" />
<TextView
android:id="@+id/tv_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="7dp"
android:text="列表"
android:textColor="@android:color/white"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_speed"
app:layout_constraintTop_toTopOf="@id/tv_detail" />
<com.yidianling.course.widget.AudioPlayView
android:id="@+id/audio_play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="28dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_detail" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:background="#9F998F">
<ImageView
android:id="@+id/iv_rewind"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/course_rewind_15"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_fast_forward"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/course_fast_forward_15"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SeekBar
android:id="@+id/seekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxHeight="3dp"
android:minHeight="3dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:progress="0"
android:progressDrawable="@drawable/course_seekbar"
android:thumb="@drawable/course_seekbar_dot"
app:layout_constraintBottom_toBottomOf="@id/iv_rewind"
app:layout_constraintEnd_toStartOf="@id/iv_fast_forward"
app:layout_constraintStart_toEndOf="@id/iv_rewind"
app:layout_constraintTop_toTopOf="@id/iv_rewind" />
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="@id/seekbar"
app:layout_constraintTop_toBottomOf="@id/seekbar"
tools:text="00:00" />
<TextView
android:id="@+id/tv_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="@id/seekbar"
app:layout_constraintTop_toBottomOf="@id/seekbar"
tools:text="00:00" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv_bg"
android:layout_width="66dp"
android:layout_height="66dp"
android:layout_marginTop="20dp"
android:background="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_progress"
app:roundPercent="1" />
<ImageView
android:id="@+id/iv_play_status"
android:layout_width="30dp"
android:layout_height="30dp"
app:layout_constraintBottom_toBottomOf="@id/iv_bg"
app:layout_constraintEnd_toEndOf="@id/iv_bg"
app:layout_constraintStart_toStartOf="@id/iv_bg"
app:layout_constraintTop_toTopOf="@id/iv_bg"
tools:src="@drawable/course_play" />
<ImageView
android:id="@+id/iv_pre"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="36dp"
android:padding="12dp"
android:src="@drawable/course_pre"
app:layout_constraintBottom_toBottomOf="@id/iv_bg"
app:layout_constraintEnd_toStartOf="@id/iv_bg"
app:layout_constraintTop_toTopOf="@id/iv_bg" />
<ImageView
android:id="@+id/iv_next"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="36dp"
android:padding="12dp"
android:src="@drawable/course_next"
app:layout_constraintBottom_toBottomOf="@id/iv_bg"
app:layout_constraintStart_toEndOf="@id/iv_bg"
app:layout_constraintTop_toTopOf="@id/iv_bg" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_top_corner">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
android:text="播放列表"
android:textColor="@color/color_1C1F28"
android:textSize="16dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_list"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_marginTop="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_top_corner"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/color_FAFAFB" />
<TextView
android:id="@+id/tv_cancel"
android:layout_width="match_parent"
android:layout_height="55dp"
android:text="取消"
android:background="@android:color/white"
android:gravity="center"
android:textStyle="bold"
android:textColor="@color/color_1C1F28"
android:textSize="18sp"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginTop="20dp"
android:paddingTop="1dp"
android:textColor="@color/color_242424"
android:textSize="14sp"
android:layout_marginEnd="8dp"
app:layout_goneMarginEnd="80dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_audition"
app:layout_constraintTop_toTopOf="parent"
tools:text="孩子是厌学情绪,还是厌学症?孩子是厌学情绪,还是厌学症?" />
<ImageView
android:id="@+id/iv_audition"
android:layout_width="32dp"
android:layout_height="16dp"
android:src="@drawable/course_audition"
android:visibility="gone"
tools:visibility="visible"
android:layout_marginEnd="50dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.yidianling.common.view.ui.VoicePlayingIcon
android:id="@+id/voice_play"
android:layout_width="15dp"
android:layout_height="16dp"
android:layout_marginEnd="22dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:pointer_color="@color/color_1DA1F2"
app:pointer_num="3"
app:pointer_width="3"
app:pointer_speed="10"/>
<ImageView
android:id="@+id/iv_play"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="18dp"
tools:src="@drawable/course_play_orange"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<View
android:layout_width="match_parent"
android:layout_height="0.25dp"
android:layout_marginTop="19.5dp"
android:layout_marginEnd="17dp"
android:layout_marginStart="17dp"
android:background="@color/color_EFEFF1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv_speed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:textColor="@color/color_1C1F28"
android:textSize="16sp"
tools:text="2.0" />
\ No newline at end of file
......@@ -13,7 +13,7 @@ import com.ydl.webview.H5Params;
import com.ydl.webview.NewH5Activity;
import com.ydl.ydl_image.module.GlideApp;
import com.ydl.ydl_image.transform.GlideCircleTransform;
import com.ydl.ydlcommon.base.BaseDialogFragment;
import com.ydl.ydlcommon.base.BaseOldDialogFragment;
import com.ydl.ydlcommon.bean.ShareData;
import com.ydl.ydlcommon.view.dialog.CommonDialog;
import com.yidianling.common.tools.ToastUtil;
......@@ -30,7 +30,7 @@ import io.reactivex.schedulers.Schedulers;
/**
* Created by jinkai on 16/5/10.
*/
public class ShowIntroduceDialogFragment extends BaseDialogFragment {
public class ShowIntroduceDialogFragment extends BaseOldDialogFragment {
private ImageView mHead;
......
......@@ -425,7 +425,7 @@ public class FMDetailActivity extends BaseActivity implements View.OnClickListen
iv_play.setImageResource(R.drawable.fm_stop_2);
fm_progress.setProgress(0);
}else {
this.time = Integer.parseInt(String.valueOf(AudioPlayer.Companion.get().getAudioPosition()));
this.time = Integer.parseInt(String.valueOf(AudioPlayer.Companion.get().getCurrentPosition()));
//PlayProgressUtil.INSTANCE.getProgress(this, fmDetail.getFm_url());
updateThread();
}
......@@ -552,8 +552,8 @@ public class FMDetailActivity extends BaseActivity implements View.OnClickListen
super.onResume();
fmSurfaceView.surfaceCreated(null);
if (PlayerFloatHelper.Companion.isShow(this)) {
PlayerFloatHelper.Companion.removeView(this);
if (PlayerFloatHelper.Companion.isShow()) {
PlayerFloatHelper.Companion.removeView();
}
}
......@@ -591,18 +591,23 @@ public class FMDetailActivity extends BaseActivity implements View.OnClickListen
}
@Override
public void onChange(@NotNull Music music) {
public boolean onPreLoad(int playPosition) {
return true;
}
@Override
public void onLoad(@NotNull Music music) {
}
@Override
public void onPlayerStart() {
public void onStartPlay() {
iv_play.setImageResource(R.drawable.fm_stop_2);
fmSurfaceView.playTimer();
}
@Override
public void onPlayerPause() {
public void onPausePlay() {
iv_play.setImageResource(R.drawable.fm_play_2);
fmSurfaceView.stopTimer();
}
......
......@@ -55,7 +55,7 @@ class FMServiceImpl : IFMService {
*/
override fun closePlayer() {
if (AudioPlayer.get().isPlaying) {
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
}
......
......@@ -507,9 +507,9 @@ open class HomeBaseImpl : IHomeBaseEvent {
}
PlayerFloatHelper.isCanClick = true
if (PlayerFloatHelper.isShow(mContext!!)) {
if (PlayerFloatHelper.isShow()) {
if (PlayerFloatHelper.playingType != PlayTypeEnum.PLAY_TYPE_FM) {
PlayerFloatHelper.removeView(mContext!!)
PlayerFloatHelper.removeView()
PlayerFloatHelper.show(mContext!!)
} else {
......
......@@ -52,9 +52,9 @@ class HomeImpl(context: Context, homeView: IHomeContract.View) : HomeBaseImpl(co
AudioPlayer.get().singlePlay(music, false)
PlayerFloatHelper.playingType = PlayTypeEnum.PLAY_TYPE_CONFIDE
if (PlayerFloatHelper.isShow(mContext!!)) {
if (PlayerFloatHelper.isShow()) {
if (PlayerFloatHelper.playingType == PlayTypeEnum.PLAY_TYPE_CONFIDE) {
PlayerFloatHelper.removeView(mContext!!)
PlayerFloatHelper.removeView()
PlayerFloatHelper.show(mContext!!)
} else {
......@@ -78,9 +78,8 @@ class HomeImpl(context: Context, homeView: IHomeContract.View) : HomeBaseImpl(co
override fun onPlayFinish() {
stopPlayAnim()
if (PlayerFloatHelper.isShow(mContext!!)) {
PlayerFloatHelper.removeView(mContext!!)
AudioPlayer.get().stopPlayer()
if (PlayerFloatHelper.isShow()) {
PlayerFloatHelper.removeResetView()
}
}
......
......@@ -648,9 +648,8 @@ public class NewH5_WVClickAbstractListener implements NewH5_WebViewClientClickLi
public void closeFloatView() {
mActivity.runOnUiThread(() -> {
try {
if (PlayerFloatHelper.Companion.isShow(mActivity)) {
PlayerFloatHelper.Companion.removeView(mActivity);
AudioPlayer.Companion.get().stopPlayer();
if (PlayerFloatHelper.Companion.isShow()) {
PlayerFloatHelper.Companion.removeResetView();
}
} catch (Exception e) {
e.printStackTrace();
......
......@@ -27,7 +27,7 @@ class MuseActivity : BaseFlutterActivity() {
* 如果正在播放音乐,则停止播放所有音乐
*/
if (AudioPlayer.get().isPlaying) {
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
}
......@@ -36,6 +36,6 @@ class MuseActivity : BaseFlutterActivity() {
/**
* 退出冥想模块的时候,关闭音乐播放
*/
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
}
......@@ -109,9 +109,7 @@ class PlayMeditationActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PlayerFloatHelper.removeView(mContext)
PlayerFloatHelper.playTempData.clear()
AudioPlayer.get().stopPlayer()
PlayerFloatHelper.removeResetView()
mMediaPlayer = MediaPlayerManager.getInstance(this)?.getMediaPlayer()
......
......@@ -71,7 +71,7 @@ class MusePlugin : MethodChannel.MethodCallHandler {
var mContinue: Boolean = methodCall.argument<Boolean>("mContinue")!!
if (mContinue) {
AudioPlayer.get().seekTo(-1, AudioPlayer.get().audioPosition)
AudioPlayer.get().seekTo(-1, AudioPlayer.get().currentPosition)
AudioPlayer.get().startPlayer()
}else {
val music = Music()
......@@ -93,7 +93,7 @@ class MusePlugin : MethodChannel.MethodCallHandler {
}
// 暂停(销毁)音乐
ACTION_STOP_MUSIC -> {
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
// 设置默认时间
ACTION_SET_DEFAULT_TIME -> {
......
package com.yidianling.muse.helper
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.graphics.Point
import android.os.Bundle
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import androidx.core.view.ViewCompat
import com.alibaba.android.arouter.launcher.ARouter
import com.ydl.media.audio.AudioPlayer
import com.ydl.media.view.PlayTypeEnum
import com.yidianling.common.tools.RxImageTool
class MeditationPlayWindowController {
companion object {
@SuppressLint("StaticFieldLeak")
private var mPlayerFloatView: MeditationFloatWindow? = null
//已添加悬浮窗页面全路径名
private var showingPageName: String? = null
private var wm: WindowManager? = null
var isCanClick = true
var playingType = PlayTypeEnum.PLAY_TYPE_NONE
var playTempData = hashMapOf<String, String>()
/**
* 如果有音频正在播放则显示
*/
fun showIfPlaying(context: Context) {
if (MediaPlayerManager.getInstance(context)?.isPlaying() == true) {
show(context)
mPlayerFloatView?.updatePlayState()
} else {
hide()
}
}
/**
* 显示悬浮控件
*/
fun show(
context: Context,
playTypeEnum: PlayTypeEnum = PlayTypeEnum.PLAY_TYPE_NONE,
playData: HashMap<String, String> = hashMapOf<String, String>()
) {
playingType = playTypeEnum
if (playData.size > 0) {
this.playTempData.putAll(playData)
}
if (mPlayerFloatView == null) {
mPlayerFloatView = MeditationFloatWindow(context)
mPlayerFloatView?.addFloatClickListener(object :
MeditationFloatWindow.FloatViewPlayListener {
override fun onPauseClick() {
}
override fun onStartClick() {
}
override fun onPlayFinish() {
}
})
}
if (showingPageName != context::class.qualifiedName) {
mPlayerFloatView?.resetWm(context)
addFloatToWm(context)
}
mPlayerFloatView?.resetView()
mPlayerFloatView?.visibility = View.VISIBLE
mPlayerFloatView?.setPlayingState()
}
fun hide() {
mPlayerFloatView?.visibility = View.GONE
}
fun addClickListener(listener: MeditationFloatWindow.FloatViewPlayListener) {
mPlayerFloatView?.addFloatClickListener(listener)
}
fun removeClickListener(listener: MeditationFloatWindow.FloatViewPlayListener) {
mPlayerFloatView?.removeFloatClickListener(listener)
}
fun isShow(context: Context): Boolean {
return !TextUtils.isEmpty(showingPageName) && showingPageName == context::class.qualifiedName && mPlayerFloatView?.visibility == View.VISIBLE
}
fun removeView(context: Context) {
if (TextUtils.isEmpty(showingPageName) || showingPageName != context::class.qualifiedName) {
return
}
mPlayerFloatView?.visibility = View.GONE
wm?.removeViewImmediate(mPlayerFloatView)
showingPageName = ""
wm = null
}
fun onDestroy() {
if (mPlayerFloatView != null) {
if (!TextUtils.isEmpty(showingPageName)) {
wm?.removeViewImmediate(mPlayerFloatView)
showingPageName = ""
}
mPlayerFloatView?.onDestroy()
mPlayerFloatView?.removeAllViews()
}
}
fun setPlayingState(context: Context) {
show(context)
mPlayerFloatView?.setPlayingState()
}
fun updatePlayState() {
mPlayerFloatView?.updatePlayState()
}
private fun addFloatToWm(context: Context) {
if (wm != null && !TextUtils.isEmpty(showingPageName)) {
if (ViewCompat.isAttachedToWindow(mPlayerFloatView!!)) {
// if (context is Activity && !(context.isFinishing)){
// wm?.removeViewImmediate(mPlayerFloatView)
// }
wm?.removeViewImmediate(mPlayerFloatView)
}
wm = null
}
//获取WindowManager
wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
//设置LayoutParams(全局变量)相关参数
val wmParams = mPlayerFloatView?.wmParams
wmParams?.type = WindowManager.LayoutParams.TYPE_APPLICATION //设置window type
wmParams?.format = PixelFormat.RGBA_8888 //设置图片格式,效果为背景透明
//设置Window flag
wmParams?.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
wmParams?.gravity = Gravity.LEFT or Gravity.TOP //调整悬浮窗口至左上角
//以屏幕左上角为原点,设置x、y初始值
val size = Point()
wm?.defaultDisplay?.getSize(size)
wmParams?.x = 0
wmParams?.y = size.y * 5 / 6
//设置悬浮窗口长宽数据
wmParams?.width = WindowManager.LayoutParams.MATCH_PARENT
wmParams?.height = RxImageTool.dp2px(56f)
//显示myFloatView图像
wm?.addView(mPlayerFloatView, wmParams)
showingPageName = context::class.qualifiedName!!
}
/**
* 打开播放中的页面详情
*/
fun startPlayingActivity(context: Context?, fullScreen: Int = 0) {
if (playingType == PlayTypeEnum.PLAY_TYPE_FM) {
//FM播放页
startFMPlayActivity(context)
} else if (playingType == PlayTypeEnum.PLAY_TYPE_COURSE) {
var url = AudioPlayer.get().playMusic?.path
//课程播放页
startCoursePlayActivity(context, 1, fullScreen, url, true)
}
}
fun startCoursePlayActivity(
context: Context?,
from: Int,
fullScreen: Int = 0,
coursePlayUrl: String? = "",
isFromFloatView: Boolean = false
) {
ARouter.getInstance()
.build("/course/play")
.withInt("course_id", playTempData["course_id"]?.toInt() ?: 0)
.withInt("course_type", 0)
.withString("coursePlayUrl", coursePlayUrl)
.withInt("from", from)
.withBoolean("isFromFloatView", isFromFloatView)
.withInt("fullScreen", fullScreen)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.navigation()
}
fun startFMPlayActivity(context: Context?) {
val bundle = Bundle()
bundle.putInt("id", playTempData["fmId"]?.toInt() ?: 0)
ARouter.getInstance().build("/fm/detail")
.withBundle("bundle", bundle)
.navigation()
}
/**
* 获取FM播放Id
*/
fun getFmId(): Int {
return playTempData["fmId"]?.toInt() ?: 0
}
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.DatePicker;
import com.ydl.ydlcommon.base.BaseDialogFragment;
import com.ydl.ydlcommon.base.BaseOldDialogFragment;
import com.yidianling.user.R;
import java.util.Calendar;
......@@ -17,7 +17,7 @@ import java.util.Calendar;
* 日期选择器 dialog
* Created by Dog on 2015/6/12.
*/
public class DatePickerDialogFragment extends BaseDialogFragment implements DatePicker.OnDateChangedListener {
public class DatePickerDialogFragment extends BaseOldDialogFragment implements DatePicker.OnDateChangedListener {
DatePicker dp_birthday;
......
......@@ -6,30 +6,34 @@ import com.ydl.media.audio.model.Music
/**
* Created by haorui on 2019-10-27 .
* Des:播放进度监听器
*
* todo: [onStartPlay]和[onPrepared]调用顺序有问题,不改是因为多处调用
*/
interface OnPlayerEventListener {
/**
* 切换歌曲
* 加载之前
* @return true: 可加载
*/
fun onChange(music: Music)
fun onPreLoad(playPosition: Int): Boolean = true
/**
* 继续播放
* 加载之后
*/
fun onPlayerStart()
fun onLoad(music: Music)
/**
* 暂停播放
* 开始播放
*
*/
fun onPlayerPause()
fun onStartPlay()
/**
* 更新进度
* percent : 播放百分比
* currentPosition:当前播放位置
* 准备完成
*
* duration:音乐时长
*/
fun onPublish(percent: Int,currentPosition: Long)
fun onPrepared(duration:Long)
/**
* 缓冲百分比
......@@ -37,14 +41,19 @@ interface OnPlayerEventListener {
fun onBufferingUpdate(percent: Int)
/**
* 准备完成
*
* duration:音乐时长
* 更新进度
* percent : 播放百分比
* currentPosition:当前播放位置
*/
fun onPrepared(duration:Long)
fun onPublish(percent: Int,currentPosition: Long)
/**
* 单曲播放完成
* 播放完成
*/
fun onComplete()
/**
* 暂停播放
*/
fun onPausePlay()
}
......@@ -4,7 +4,9 @@ package com.ydl.media.audio.enums
* Created by haorui on 2019-10-27 .
* Des: 播放模式
*/
enum class PlayModeEnum private constructor(private val value: Int) {
enum class PlayModeEnum(private val value: Int) {
//列表
LIST(-1),
//列表循环
LIST_LOOP(0),
//随机播放
......
......@@ -14,6 +14,7 @@ import com.ydl.media.audio.utils.CoverImageUtils
/**
* Created by haorui on 2019-10-27 .
* Des:
* 线控
*/
class MediaSessionManager private constructor() {
......@@ -22,11 +23,11 @@ class MediaSessionManager private constructor() {
private val callback = object : MediaSessionCompat.Callback() {
override fun onPlay() {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
override fun onPause() {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
override fun onSkipToNext() {
......@@ -38,7 +39,7 @@ class MediaSessionManager private constructor() {
}
override fun onStop() {
AudioPlayer.get().stopPlayer()
AudioPlayer.get().resetPlayer()
}
override fun onSeekTo(pos: Long) {
......@@ -68,7 +69,7 @@ class MediaSessionManager private constructor() {
mediaSession?.setPlaybackState(
PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(state, AudioPlayer.get().audioPosition, 1f)
.setState(state, AudioPlayer.get().currentPosition, 1f)
.build()
)
}
......
......@@ -21,6 +21,7 @@ import com.ydl.media.R
import com.ydl.media.audio.constants.Extras
import com.ydl.media.audio.model.Music
import com.ydl.media.audio.receiver.StatusBarReceiver
import com.ydl.ydlcommon.router.YdlCommonOut
import java.util.*
......@@ -75,20 +76,18 @@ class NotifyManager private constructor() {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
var remoteViews = getRemoteViews(context, music, isPlaying)
val remoteViews = getRemoteViews(context, music, isPlaying)
val builder: NotificationCompat.Builder
if (Build.VERSION.SDK_INT >= 26) {
val builder: NotificationCompat.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "app_play_intent_service_channel_id"
val channelName = "壹点灵心理咨询"
val notificationManager =
context.getSystemService("notification") as NotificationManager
val notificationChannel = NotificationChannel(channelId, channelName, 2)
notificationManager.createNotificationChannel(notificationChannel)
builder = NotificationCompat.Builder(context, channelId)
NotificationCompat.Builder(context, channelId)
} else {
builder = NotificationCompat.Builder(context)
NotificationCompat.Builder(context)
}
builder
......@@ -110,7 +109,7 @@ class NotifyManager private constructor() {
private fun getRemoteViews(context: Context, music: Music, isPlaying: Boolean): RemoteViews {
val title = music.title
val subtitle = music.artist + "-" + music.album
val subtitle = music.artist + if (null == music.album) "" else "-" + music.album
val remoteViews = RemoteViews(context.packageName, R.layout.notification)
......@@ -120,7 +119,7 @@ class NotifyManager private constructor() {
val isLightNotificationTheme = isLightNotificationTheme(mContext)
val playIntent = Intent(StatusBarReceiver.ACTION_STATUS_BAR)
playIntent.setPackage("com.cxzapp.xinlizixun")
playIntent.setPackage(YdlCommonOut.getApp().packageName)
playIntent.putExtra(StatusBarReceiver.EXTRA, StatusBarReceiver.EXTRA_PLAY_PAUSE)
val playPendingIntent =
PendingIntent.getBroadcast(context, 0, playIntent, PendingIntent.FLAG_UPDATE_CURRENT)
......@@ -131,7 +130,7 @@ class NotifyManager private constructor() {
remoteViews.setOnClickPendingIntent(R.id.iv_play_pause, playPendingIntent)
val nextIntent = Intent(StatusBarReceiver.ACTION_STATUS_BAR)
nextIntent.setPackage("com.cxzapp.xinlizixun")
nextIntent.setPackage(YdlCommonOut.getApp().packageName)
nextIntent.putExtra(StatusBarReceiver.EXTRA, StatusBarReceiver.EXTRA_NEXT)
val nextPendingIntent =
PendingIntent.getBroadcast(context, 1, nextIntent, PendingIntent.FLAG_UPDATE_CURRENT)
......
......@@ -17,6 +17,8 @@ class Music : Serializable {
var duration: Long = 0 // 持续时间
var path: String? = null // 播放地址
var canPlay: Boolean = true // 是否能播放
override fun equals(o: Any?): Boolean {
if (o !is Music) {
return false
......
......@@ -14,6 +14,6 @@ import com.ydl.media.audio.AudioPlayer
class NoisyAudioStreamReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
}
......@@ -21,7 +21,7 @@ class RemoteControlReceiver : BroadcastReceiver() {
}
when (event.keyCode) {
KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> AudioPlayer.get().playPause()
KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> AudioPlayer.get().playOrPause()
KeyEvent.KEYCODE_MEDIA_NEXT -> AudioPlayer.get().next()
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> AudioPlayer.get().prev()
}
......
......@@ -23,7 +23,7 @@ class StatusBarReceiver : BroadcastReceiver() {
if (TextUtils.equals(extra, EXTRA_NEXT)) {
AudioPlayer.get().next()
} else if (TextUtils.equals(extra, EXTRA_PLAY_PAUSE)) {
AudioPlayer.get().playPause()
AudioPlayer.get().playOrPause()
}
}
......
......@@ -14,10 +14,10 @@ object PlayProgressUtil {
*/
fun saveProgress(context: Context?,url : String?,progress : Int){
if (TextUtils.isEmpty(url))return
var shared = context?.getSharedPreferences("AUDIO_PLAYER_MUSIC_PROGRESS",Context.MODE_PRIVATE)
var edit = shared?.edit()
val shared = context?.getSharedPreferences("AUDIO_PLAYER_MUSIC_PROGRESS",Context.MODE_PRIVATE)
val edit = shared?.edit()
edit?.putInt(url,progress)
edit?.commit()
edit?.apply()
}
/**
......@@ -25,7 +25,7 @@ object PlayProgressUtil {
*/
fun getProgress(context: Context?,url : String?):Int{
if (TextUtils.isEmpty(url))return 0
var shared = context?.getSharedPreferences("AUDIO_PLAYER_MUSIC_PROGRESS",Context.MODE_PRIVATE)
val shared = context?.getSharedPreferences("AUDIO_PLAYER_MUSIC_PROGRESS",Context.MODE_PRIVATE)
return shared?.getInt(url,0)?:0
}
}
\ No newline at end of file
package com.ydl.media.view
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.graphics.Point
import android.os.Bundle
import androidx.core.view.ViewCompat
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import com.alibaba.android.arouter.launcher.ARouter
import com.ydl.media.audio.AudioPlayer
import com.yidianling.common.tools.RxImageTool
/**
* 音频播放悬浮窗 帮助类
......@@ -24,76 +15,35 @@ import com.yidianling.common.tools.RxImageTool
class PlayerFloatHelper {
companion object {
@SuppressLint("StaticFieldLeak")
private var mPlayerFloatView: PlayerFloatView? = null
//已添加悬浮窗页面全路径名
private var showingPageName: String? = null
private var wm: WindowManager? = null
var isCanClick = true
var playingType = PlayTypeEnum.PLAY_TYPE_NONE
var playTempData = hashMapOf<String,String>()
/**
* FM:
* fmId
* fmTitle
* fmAuthor
* fmImageUrl
*/
/**
* 如果有音频正在播放则显示
*/
fun showIfPlaying(context: Context) {
if (AudioPlayer.get().isPlaying) {
show(context)
mPlayerFloatView?.updatePlayState()
} else {
hide()
removeView()
}
}
/**
* 显示悬浮控件
*/
fun show(context: Context, playTypeEnum: PlayTypeEnum = PlayTypeEnum.PLAY_TYPE_NONE, playData:HashMap<String,String> = hashMapOf<String,String>()) {
playingType = playTypeEnum
if (playData.size>0){
fun show(context: Context, playTypeEnum: PlayTypeEnum = PlayTypeEnum.PLAY_TYPE_NONE, playData:HashMap<String,String> = hashMapOf()) {
if (playData.size > 0) {
this.playTempData.putAll(playData)
}
if (mPlayerFloatView == null) {
mPlayerFloatView = PlayerFloatView(context)
mPlayerFloatView?.addFloatClickListener(object :PlayerFloatView.FloatViewPlayListener{
override fun onPauseClick() {
}
override fun onStartClick() {
}
override fun onPlayFinish() {
}
})
}
if (showingPageName != context::class.qualifiedName) {
mPlayerFloatView?.resetWm(context)
addFloatToWm(context)
}
mPlayerFloatView?.resetView()
mPlayerFloatView?.visibility = View.VISIBLE
mPlayerFloatView?.setPlayingState()
playingType = playTypeEnum
mPlayerFloatView = PlayerFloatView(context).show()
}
fun hide() {
mPlayerFloatView?.visibility = View.GONE
mPlayerFloatView?.updatePlayState()
}
fun addClickListener(listener: PlayerFloatView.FloatViewPlayListener) {
......@@ -104,31 +54,32 @@ class PlayerFloatHelper {
mPlayerFloatView?.removeFloatClickListener(listener)
}
fun isShow(context: Context): Boolean {
return !TextUtils.isEmpty(showingPageName) && showingPageName == context::class.qualifiedName && mPlayerFloatView?.visibility == View.VISIBLE
fun isShow(): Boolean {
return null != mPlayerFloatView
}
fun removeView(context: Context) {
if (TextUtils.isEmpty(showingPageName) || showingPageName != context::class.qualifiedName) {
return
}
mPlayerFloatView?.visibility = View.GONE
wm?.removeViewImmediate(mPlayerFloatView)
showingPageName = ""
wm = null
/**
* 只移除悬浮窗
*/
fun removeView() {
playTempData.clear()
mPlayerFloatView?.let {
it.remove()
it.removeAllViews()
}
fun onDestroy() {
if (mPlayerFloatView != null) {
if (!TextUtils.isEmpty(showingPageName)) {
wm?.removeViewImmediate(mPlayerFloatView)
showingPageName = ""
mPlayerFloatView = null
}
mPlayerFloatView?.onDestroy()
mPlayerFloatView?.removeAllViews()
/**
* 移除悬浮窗 + 重置音频播放器
*/
fun removeResetView() {
playTempData.clear()
mPlayerFloatView?.let {
it.removeAndReset()
it.removeAllViews()
}
mPlayerFloatView = null
}
fun setPlayingState(context: Context) {
......@@ -140,57 +91,35 @@ class PlayerFloatHelper {
mPlayerFloatView?.updatePlayState()
}
private fun addFloatToWm(context: Context) {
if (wm != null && !TextUtils.isEmpty(showingPageName)) {
if (ViewCompat.isAttachedToWindow(mPlayerFloatView!!)){
// if (context is Activity && !(context.isFinishing)){
// wm?.removeViewImmediate(mPlayerFloatView)
// }
wm?.removeViewImmediate(mPlayerFloatView)
}
wm = null
}
//获取WindowManager
wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
//设置LayoutParams(全局变量)相关参数
val wmParams = mPlayerFloatView?.wmParams
wmParams?.type = WindowManager.LayoutParams.TYPE_APPLICATION //设置window type
wmParams?.format = PixelFormat.RGBA_8888 //设置图片格式,效果为背景透明
//设置Window flag
wmParams?.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
wmParams?.gravity = Gravity.LEFT or Gravity.TOP //调整悬浮窗口至左上角
//以屏幕左上角为原点,设置x、y初始值
val size = Point()
wm?.defaultDisplay?.getSize(size)
wmParams?.x = 0
wmParams?.y = size.y * 5 / 6
//设置悬浮窗口长宽数据
wmParams?.width = WindowManager.LayoutParams.MATCH_PARENT
wmParams?.height = RxImageTool.dp2px(56f)
//显示myFloatView图像
wm?.addView(mPlayerFloatView, wmParams)
showingPageName = context::class.qualifiedName!!
}
/**
* 打开播放中的页面详情
*/
fun startPlayingActivity(context: Context?, fullScreen: Int = 0) {
if (playingType == PlayTypeEnum.PLAY_TYPE_FM) {
//FM播放页
startFMPlayActivity(context)
startFMPlayActivity()
} else if (playingType == PlayTypeEnum.PLAY_TYPE_COURSE) {
var url = AudioPlayer.get().playMusic?.path
val url = AudioPlayer.get().playMusic?.path
//课程播放页
startCoursePlayActivity(context, 1, fullScreen, url, true)
}
}
fun startCoursePlayActivity(context: Context?, from: Int, fullScreen: Int = 0, coursePlayUrl: String? = "", isFromFloatView: Boolean = false) {
val courseId = playTempData["course_id"]?.toInt() ?: 0
if ("1" == playTempData["media_type"]) { // 音频
ARouter.getInstance()
.build("/course/audioPlay")
.withInt("course_id", courseId)
.withString("coursePlayUrl", coursePlayUrl)
.withInt("from", from)
.withBoolean("isFromFloatView", isFromFloatView)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.navigation()
} else {
ARouter.getInstance()
.build("/course/play")
.withInt("course_id", playTempData["course_id"]?.toInt()?:0)
.withInt("course_id", courseId)
.withInt("course_type", 0)
.withString("coursePlayUrl", coursePlayUrl)
.withInt("from", from)
......@@ -199,8 +128,9 @@ class PlayerFloatHelper {
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.navigation()
}
}
fun startFMPlayActivity(context: Context?) {
private fun startFMPlayActivity() {
val bundle = Bundle()
bundle.putInt("id", playTempData["fmId"]?.toInt()?:0)
ARouter.getInstance().build("/fm/detail")
......@@ -215,9 +145,6 @@ class PlayerFloatHelper {
return playTempData["fmId"]?.toInt()?:0
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
<LinearLayout
android:id="@+id/ll_audio_layout"
android:id="@+id/cl"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/play_float_background"
android:gravity="center_vertical"
android:orientation="horizontal">
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:background="@drawable/play_float_background">
<ImageView
android:id="@+id/play_close"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_marginLeft="12.5dp"
android:layout_marginRight="@dimen/platform_dp_25"
android:padding="2.5dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:padding="15dp"
android:src="@drawable/ico_play_float_pause"
android:visibility="visible" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/play_head"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginLeft="12.5dp"
android:src="@drawable/ico_play_float_pic" />
<RelativeLayout
android:id="@+id/rl_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/platform_dp_18"
android:layout_marginRight="@dimen/platform_dp_18"
android:layout_weight="1">
android:layout_marginEnd="12dp"
android:src="@drawable/ico_play_float_pic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_title"
app:layout_constraintStart_toEndOf="@id/play_close"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#FFFFFF"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/tv_name"
app:layout_constraintEnd_toStartOf="@id/play_state"
app:layout_constraintStart_toEndOf="@id/play_head"
tools:text="丁sir说抑郁丁sir说抑郁:像心理学" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#BFBFBF"
android:textSize="10sp"
android:maxLength="10"
tools:text="丁建略丁建略丁建略丁建略" />
<TextView
android:id="@+id/tv_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_toRightOf="@+id/tv_name"
android:text=" | "
android:textColor="#BFBFBF"
android:textSize="10sp" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/tv_title"
app:layout_constraintStart_toStartOf="@id/tv_title"
app:layout_constraintTop_toTopOf="parent"
tools:text="丁sir" />
<TextView
android:id="@+id/tv_now_playing_time"
android:id="@+id/tv_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_toRightOf="@+id/tv_line"
app:layout_constraintStart_toStartOf="@id/tv_title"
app:layout_constraintTop_toBottomOf="@id/tv_name"
android:textColor="#BFBFBF"
android:textSize="10sp"
tools:text="00:06" />
<TextView
android:id="@+id/tv_line_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_toRightOf="@+id/tv_now_playing_time"
android:text=" / "
android:textColor="#BFBFBF"
android:textSize="10sp" />
<TextView
android:id="@+id/tv_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_toRightOf="@+id/tv_line_two"
app:layout_constraintBaseline_toBaselineOf="@id/tv_start"
app:layout_constraintStart_toEndOf="@id/tv_start"
android:textColor="#BFBFBF"
android:textSize="10sp"
tools:text="30:28" />
</RelativeLayout>
tools:text=" / 30:28" />
<ImageView
android:id="@+id/play_state"
android:layout_width="30dp"
android:layout_width="57dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:layout_marginRight="15dp"
android:paddingStart="12dp"
android:paddingEnd="15dp"
android:scaleType="centerCrop"
android:src="@drawable/ico_yyfc_play" />
</LinearLayout>
android:src="@drawable/ico_yyfc_play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_title"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
\ No newline at end of file
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -4,12 +4,13 @@ import android.app.Activity
import android.content.res.Resources
import android.graphics.Color
import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import com.trello.rxlifecycle2.android.ActivityEvent
import com.ydl.ydlcommon.R
import com.ydl.ydlcommon.base.lifecycle.IActivityLifecycleable
......@@ -18,6 +19,7 @@ import com.ydl.ydlcommon.ui.LoadingDialogFragment
import com.ydl.ydlcommon.utils.ActivityManager
import com.ydl.ydlcommon.utils.AndroidSystemHelper
import com.ydl.ydlcommon.utils.StatusBarUtils
import com.ydl.ydlcommon.utils.statusBar.StatusBarUtil
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
import kotlin.properties.Delegates
......@@ -42,6 +44,7 @@ abstract class BaseActivity : AppCompatActivity(), IActivityLifecycleable {
override fun onCreate(savedInstanceState: Bundle?) {
//修复Android 8.0 崩溃
AndroidSystemHelper.fixAndroidOrientationBug(this)
setImmersiveStatusBar()
super.onCreate(savedInstanceState)
Log.d("TAG", javaClass.name)
......@@ -53,6 +56,28 @@ abstract class BaseActivity : AppCompatActivity(), IActivityLifecycleable {
initDataAndEvent()
}
/**
* 开启沉浸式状态栏
* 默认关闭
*/
open fun turnOnImmersiveStatusBar() = false
/**
* true: 黑色字体
* false: 白色字体
*/
open fun setStatusBarBlackFont() = false
private fun setImmersiveStatusBar() {
if (turnOnImmersiveStatusBar()) {
StatusBarUtil.setTranslucent(window)
if (setStatusBarBlackFont())
StatusBarUtil.statusBarDarkMode(this)
else
StatusBarUtil.statusBarLightMode(this)
}
}
override fun setRequestedOrientation(requestedOrientation: Int) {
if (AndroidSystemHelper.isAllowSetOrientation(this)) {
super.setRequestedOrientation(requestedOrientation)
......@@ -60,7 +85,7 @@ abstract class BaseActivity : AppCompatActivity(), IActivityLifecycleable {
}
override fun setContentView(@LayoutRes layoutResID: Int) {
if (getStatusViewOptions()?.isAddStatusView) {
if (getStatusViewOptions().isAddStatusView) {
val options = getStatusViewOptions()
options.bottomStatusColor = getDefaultBottomColor()
val returnViews =
......@@ -183,4 +208,22 @@ abstract class BaseActivity : AppCompatActivity(), IActivityLifecycleable {
}
/**
* 展示 Fragment
* 防止崩溃异常 -> java.lang.IllegalStateException: Fragment already added
*/
fun showFragment(dialogFragment: DialogFragment?, tag: String) {
dialogFragment?.let {
val transaction = supportFragmentManager.beginTransaction()
if (it.isAdded && supportFragmentManager.findFragmentByTag(tag) != null) {
transaction.show(it)
} else {
supportFragmentManager.executePendingTransactions()
transaction.remove(dialogFragment) // 避免 Fragment already added
transaction.add(it, tag)
}
transaction.commitAllowingStateLoss()
}
}
}
\ No newline at end of file
package com.ydl.ydlcommon.base
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewDebug.ExportedProperty
import android.view.ViewDebug.IntToString
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.fragment.app.DialogFragment
import com.ydl.ydlcommon.R
/**
* 弹窗基类
*/
abstract class BaseDialogFragment : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.bottomUpDialog)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(getLayoutResourceId(), container, false)
}
override fun onStart() {
super.onStart()
dialog?.window?.run {
attributes = attributes?.apply {
gravity = getGravity()
width = getWidth()
height = getHeight()
}
}
}
@LayoutRes
abstract fun getLayoutResourceId(): Int
open fun getGravity(): Int = Gravity.NO_GRAVITY
@ExportedProperty(
category = "layout",
mapping = [IntToString(
from = ViewGroup.LayoutParams.MATCH_PARENT,
to = "MATCH_PARENT"
), IntToString(from = ViewGroup.LayoutParams.WRAP_CONTENT, to = "WRAP_CONTENT")]
)
open fun getWidth(): Int = ViewGroup.LayoutParams.MATCH_PARENT
@ExportedProperty(
category = "layout",
mapping = [IntToString(
from = ViewGroup.LayoutParams.MATCH_PARENT,
to = "MATCH_PARENT"
), IntToString(from = ViewGroup.LayoutParams.WRAP_CONTENT, to = "WRAP_CONTENT")]
)
open fun getHeight(): Int = ViewGroup.LayoutParams.WRAP_CONTENT
}
\ No newline at end of file
......@@ -17,7 +17,7 @@ import com.ydl.ydlcommon.R;
* 基础dialog fragment
* Created by Dog on 2015/8/6.
*/
public class BaseDialogFragment extends DialogFragment {
public class BaseOldDialogFragment extends DialogFragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
......
@file:JvmName("ImageExt")
@file:Suppress("unused")
import android.content.*
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import com.ydl.ydlcommon.utils.extend.ifNotNull
import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
private const val TAG = "ImageExt"
private val ALBUM_DIR = Environment.DIRECTORY_PICTURES
private class OutputFileTaker(var file: File? = null)
/**
* 复制图片文件到相册的Pictures文件夹
*
* @param context 上下文
* @param fileName 文件名。 需要携带后缀
* @param relativePath 相对于Pictures的路径
*/
fun File.copyToAlbum(context: Context, fileName: String, relativePath: String?): Uri? {
if (!this.canRead() || !this.exists()) {
Log.w(TAG, "check: read file error: $this")
return null
}
return this.inputStream().use {
it.saveToAlbum(context, fileName, relativePath)
}
}
/**
* 保存图片Stream到相册的Pictures文件夹
*
* @param context 上下文
* @param fileName 文件名。 需要携带后缀
* @param relativePath 相对于Pictures的路径
*/
fun InputStream.saveToAlbum(context: Context, fileName: String, relativePath: String?): Uri? {
val resolver = context.contentResolver
val outputFile = OutputFileTaker()
val imageUri = resolver.insertMediaImage(fileName, relativePath, outputFile)
if (imageUri == null) {
Log.w(TAG, "insert: error: uri == null")
return null
}
(imageUri.outputStream(resolver) ?: return null).use { output ->
this.use { input ->
input.copyTo(output)
imageUri.finishPending(context, resolver, outputFile.file)
}
}
return imageUri
}
/**
* 保存Bitmap到相册的Pictures文件夹
*
* https://developer.android.google.cn/training/data-storage/shared/media
*
* @param context 上下文
* @param fileName 文件名。 需要携带后缀
* @param relativePath 相对于Pictures的路径
* @param quality 质量
*/
fun Bitmap.saveToAlbum(
context: Context,
fileName: String,
relativePath: String? = null,
quality: Int = 100
): Uri? {
// 插入图片信息
val resolver = context.contentResolver
val outputFile = OutputFileTaker()
val imageUri = resolver.insertMediaImage(fileName, relativePath, outputFile)
if (imageUri == null) {
Log.w(TAG, "insert: error: uri == null")
return null
}
// 保存图片
(imageUri.outputStream(resolver) ?: return null).use {
val format = fileName.getBitmapFormat()
this@saveToAlbum.compress(format, quality, it)
imageUri.finishPending(context, resolver, outputFile.file)
}
return imageUri
}
private fun Uri.outputStream(resolver: ContentResolver): OutputStream? {
return try {
resolver.openOutputStream(this)
} catch (e: FileNotFoundException) {
Log.e(TAG, "save: open stream error: $e")
null
}
}
private fun Uri.finishPending(
context: Context,
resolver: ContentResolver,
outputFile: File?
) {
val imageValues = ContentValues()
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (outputFile != null) {
imageValues.put(MediaStore.Images.Media.SIZE, outputFile.length())
}
resolver.update(this, imageValues, null, null)
// 通知媒体库更新
val intent = Intent(@Suppress("DEPRECATION") Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, this)
context.sendBroadcast(intent)
// } else {
// Android Q添加了IS_PENDING状态,为0时其他应用才可见
// imageValues.put(MediaStore.Images.Media.IS_PENDING, 0)
// resolver.update(this, imageValues, null, null)
// }
}
private fun String.getBitmapFormat(): Bitmap.CompressFormat {
val fileName = this.toLowerCase(Locale.getDefault())
return when {
fileName.endsWith(".png") -> Bitmap.CompressFormat.PNG
fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") -> Bitmap.CompressFormat.JPEG
fileName.endsWith(".webp") ->
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
// Bitmap.CompressFormat.WEBP_LOSSLESS
// else
Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.PNG
}
}
private fun String.getMimeType(): String? {
val fileName = this.toLowerCase(Locale.getDefault())
return when {
fileName.endsWith(".png") -> "image/png"
fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") -> "image/jpeg"
fileName.endsWith(".webp") -> "image/webp"
fileName.endsWith(".gif") -> "image/gif"
else -> null
}
}
/**
* 插入图片到媒体库
*/
private fun ContentResolver.insertMediaImage(
fileName: String,
relativePath: String?,
outputFileTaker: OutputFileTaker? = null
): Uri? {
// 图片信息
val imageValues = ContentValues().apply {
val mimeType = fileName.getMimeType()
if (mimeType != null) {
put(MediaStore.Images.Media.MIME_TYPE, mimeType)
}
val date = System.currentTimeMillis() / 1000
put(MediaStore.Images.Media.DATE_ADDED, date)
put(MediaStore.Images.Media.DATE_MODIFIED, date)
}
// 保存的位置
val collection: Uri
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// val path = if (relativePath != null) "$ALBUM_DIR/${relativePath}" else ALBUM_DIR
// imageValues.apply {
// put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
// put(MediaStore.Images.Media.RELATIVE_PATH, path)
// put(MediaStore.Images.Media.IS_PENDING, 1)
// }
// collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
// // 高版本不用查重直接插入,会自动重命名
// } else {
// 老版本
val pictures =
@Suppress("DEPRECATION") Environment.getExternalStoragePublicDirectory(ALBUM_DIR)
val saveDir = if (relativePath != null) File(pictures, relativePath) else pictures
if (!saveDir.exists() && !saveDir.mkdirs()) {
Log.e(TAG, "save: error: can't create Pictures directory")
return null
}
// 文件路径查重,重复的话在文件名后拼接数字
var imageFile = File(saveDir, fileName)
val fileNameWithoutExtension = imageFile.nameWithoutExtension
val fileExtension = imageFile.extension
var queryUri = this.queryMediaImage28(imageFile.absolutePath)
var suffix = 1
while (queryUri != null) {
val newName = fileNameWithoutExtension + "(${suffix++})." + fileExtension
imageFile = File(saveDir, newName)
queryUri = this.queryMediaImage28(imageFile.absolutePath)
}
imageValues.apply {
put(MediaStore.Images.Media.DISPLAY_NAME, imageFile.name)
// 保存路径
val imagePath = imageFile.absolutePath
Log.v(TAG, "save file: $imagePath")
put(@Suppress("DEPRECATION") MediaStore.Images.Media.DATA, imagePath)
}
outputFileTaker?.file = imageFile// 回传文件路径,用于设置文件大小
collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// }
// 插入图片信息
return this.insert(collection, imageValues)
}
/**
* Android Q以下版本,查询媒体库中当前路径是否存在
* @return Uri 返回null时说明不存在,可以进行图片插入逻辑
*/
private fun ContentResolver.queryMediaImage28(imagePath: String): Uri? {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return null
val imageFile = File(imagePath)
if (imageFile.canRead() && imageFile.exists()) {
Log.v(TAG, "query: path: $imagePath exists")
// 文件已存在,返回一个file://xxx的uri
return Uri.fromFile(imageFile)
}
// 保存的位置
val collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// 查询是否已经存在相同图片
val query = this.query(
collection,
arrayOf(MediaStore.Images.Media._ID, @Suppress("DEPRECATION") MediaStore.Images.Media.DATA),
"${@Suppress("DEPRECATION") MediaStore.Images.Media.DATA} == ?",
arrayOf(imagePath), null
)
query?.use {
while (it.moveToNext()) {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val id = it.getLong(idColumn)
val existsUri = ContentUris.withAppendedId(collection, id)
Log.v(TAG, "query: path: $imagePath exists uri: $existsUri")
return existsUri
}
}
return null
}
package com.ydl.ydlcommon.utils;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.util.Base64;
import com.yidianling.common.tools.ToastUtil;
import java.io.*;
public class ImageUtil {
public static boolean savePicture(Context context, String base64DataStr) {
// 去掉base64中的前缀
// String base64Str = base64DataStr.substring(base64DataStr.indexOf(",")+1, base64DataStr.length());
File appDir = new File(Environment.getExternalStorageDirectory(), "Camera");// 图片保存的文件夹的名称
if (!appDir.exists()) {
appDir.mkdir();
}
String imgName = System.currentTimeMillis() + ".png";
File fileTest = new File(appDir, imgName);
byte[] data = Base64.decode(base64DataStr, Base64.DEFAULT);
for (int i = 0; i < data.length; i++) {
if (data[i] < 0) {
data[i] += 256;//调整异常数据
}
}
OutputStream os = null;
try {
os = new FileOutputStream(fileTest);
os.write(data);
os.flush();
os.close();
// 通知系统刷新图库
updateAlbum(context, fileTest);
ToastUtil.toastShort("图片已保存至相册");
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
} catch (IOException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
}
}
/**
* 通知图库更新数据
* context
* fileName
* file
*/
private static void updateAlbum(Context context, File file) {
// 最后通知图库更新
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
}
public static boolean savePicture(Context context, Bitmap bitmap) {
if (bitmap == null) {
return false;
}
// 去掉base64中的前缀
// String base64Str = base64DataStr.substring(base64DataStr.indexOf(",")+1, base64DataStr.length());
File appDir = new File(Environment.getExternalStorageDirectory(), "Camera");// 图片保存的文件夹的名称
if (!appDir.exists()) {
appDir.mkdir();
}
String imgName = System.currentTimeMillis() + ".png";
File fileTest = new File(appDir, imgName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
byte[] data = byteArrayOutputStream.toByteArray();
for (int i = 0; i < data.length; i++) {
if (data[i] < 0) {
data[i] += 256;//调整异常数据
}
}
OutputStream os = null;
try {
os = new FileOutputStream(fileTest);
os.write(data);
os.flush();
os.close();
// 通知系统刷新图库
updateAlbum(context, fileTest);
ToastUtil.toastShort("图片已保存至相册");
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
} catch (IOException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
}
}
}
package com.ydl.ydlcommon.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Base64
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.yidianling.common.tools.ToastUtil
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import saveToAlbum
class ImageUtil {
companion object {
private fun saveToAlbum(context: Context, bitmap: Bitmap?): Disposable {
return Observable.create<Boolean> {
bitmap?.let { pic ->
pic.saveToAlbum(
context,
"${System.currentTimeMillis()}.png"
)
it.onNext(true)
} ?: let { _ ->
it.onNext(false)
}
}.subscribeOn(Schedulers.io())
.subscribe {
if (it) {
ToastUtil.toastShort("图片已保存至相册")
}
else ToastUtil.toastShort("保存失败")
}
}
private fun convertBase64ToBitmap(base64: String): Bitmap? {
val decode = Base64.decode(base64, Base64.DEFAULT)
return BitmapFactory.decodeByteArray(decode, 0, decode.size)
}
/**
* base64字符串
* 保存图片到相册
*/
@JvmStatic
fun savePicture(context: Context, base64: String?): Disposable? {
var disposable: Disposable? = null
base64?.run {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// disposable = saveToAlbum(context, convertBase64ToPic(this))
// } else {
val permissions =
arrayOf(Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE)
if (XXPermissions.isGrantedPermission(context, permissions)) {
disposable = saveToAlbum(context, convertBase64ToBitmap(this))
} else {
XXPermissions.with(context)
.permission(permissions)
.request { _, _ ->
disposable = saveToAlbum(context, convertBase64ToBitmap(this))
}
}
// }
}
return disposable
}
/**
* bitmap
* 保存图片到相册
*/
@JvmStatic
fun savePicture(context: Context, bitmap: Bitmap?): Disposable? {
var disposable: Disposable? = null
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// disposable = saveToAlbum(context, bitmap)
// } else {
val permissions =
arrayOf(Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE)
if (XXPermissions.isGrantedPermission(context, permissions)) {
disposable = saveToAlbum(context, bitmap)
} else {
XXPermissions.with(context)
.permission(permissions)
.request { _, _ ->
disposable = saveToAlbum(context, bitmap)
}
}
// }
return disposable
}
}
}
\ No newline at end of file
package com.ydl.ydlcommon.utils.extend
import android.content.res.Resources
import android.text.TextUtils
inline fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (t1: T1, t2: T2) -> Unit) {
if (value1 != null && value2 != null) {
bothNotNull(value1, value2)
}
}
/**
* Map 扩展
*/
fun <K, V> Map<K, V>?.getIntOr(key: K, default: Int = 0): Int {
return if (this != null) ((this[key] as? Int)?: default) else default
}
fun <K, V> Map<K, V>?.getStringOr(key: K, default: String = ""): String {
return if (this != null) ((this[key] as? String)?: default) else default
}
fun Any?.asString(default: String = ""): String {
return (this as? String) ?: default
}
inline fun String?.notEmpty(block: (String) -> Unit) {
if (this != null && this.isNotEmpty()) block(this)
}
fun String?.notEqual(other: String?): Boolean = !TextUtils.equals(this, other)
inline fun String?.notNullNotEmpty(block: (String) -> Unit) {
if (this != null && this.isNotEmpty() && this.notEqual("null")) block(this)
}
fun Number?.initZero(): Number = this ?: 0 as Number
// ---------------- ⬇️ dp px 互转 ⬇️ ------------------
val Float.dp: Float
get() = this * Resources.getSystem().displayMetrics.density
val Int.dp: Float
get() = (this * Resources.getSystem().displayMetrics.density)
// ---------------- ⬆️ dp px 互转 ️⬆️ -----------------
package com.ydl.ydlcommon.utils.extend
import android.view.View
import android.widget.TextView
import androidx.annotation.DrawableRes
fun View?.gone() {
this?.visibility = View.GONE
}
fun View?.visible() {
this?.visibility = View.VISIBLE
}
fun View?.invisible() {
this?.visibility = View.INVISIBLE
}
fun TextView?.leftDrawable(@DrawableRes left: Int) {
this?.setCompoundDrawablesWithIntrinsicBounds(left, 0, 0, 0)
}
fun TextView?.topDrawable(@DrawableRes top: Int) {
this?.setCompoundDrawablesWithIntrinsicBounds(0, top, 0, 0)
}
fun TextView?.rightDrawable(@DrawableRes right: Int) {
this?.setCompoundDrawablesWithIntrinsicBounds(0, 0, right, 0)
}
fun TextView?.bottomDrawable(@DrawableRes bottom: Int) {
this?.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, bottom)
}
fun TextView?.drawable(@DrawableRes left: Int = 0, @DrawableRes top: Int = 0, @DrawableRes right: Int = 0, @DrawableRes bottom: Int = 0) {
this?.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom)
}
package com.ydl.ydlcommon.utils.statusBar;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 魅族状态栏适配工具
*/
public class FlymeStatusbarUtils {
private static Method mSetStatusBarColorIcon;
private static Method mSetStatusBarDarkIcon;
private static Field mStatusBarColorFiled;
private static int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0;
static {
try {
mSetStatusBarColorIcon = Activity.class.getMethod("setStatusBarDarkIcon", int.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
mSetStatusBarDarkIcon = Activity.class.getMethod("setStatusBarDarkIcon", boolean.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
mStatusBarColorFiled = WindowManager.LayoutParams.class.getField("statusBarColor");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
try {
Field field = View.class.getField("SYSTEM_UI_FLAG_LIGHT_STATUS_BAR");
SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = field.getInt(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 判断颜色是否偏黑色
*
* @param color 颜色
* @param level 级别
* @return
*/
public static boolean isBlackColor(int color, int level) {
int grey = toGrey(color);
return grey < level;
}
/**
* 颜色转换成灰度值
*
* @param rgb 颜色
* @return 灰度值
*/
public static int toGrey(int rgb) {
int blue = rgb & 0x000000FF;
int green = (rgb & 0x0000FF00) >> 8;
int red = (rgb & 0x00FF0000) >> 16;
return (red * 38 + green * 75 + blue * 15) >> 7;
}
/**
* 设置状态栏字体图标颜色
*
* @param activity 当前activity
* @param color 颜色
*/
public static void setStatusBarDarkIcon(Activity activity, int color) {
if (mSetStatusBarColorIcon != null) {
try {
mSetStatusBarColorIcon.invoke(activity, color);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} else {
boolean whiteColor = isBlackColor(color, 50);
if (mStatusBarColorFiled != null) {
setStatusBarDarkIcon(activity, whiteColor, whiteColor);
setStatusBarDarkIcon(activity.getWindow(), color);
} else {
setStatusBarDarkIcon(activity, whiteColor);
}
}
}
/**
* 设置状态栏字体图标颜色(只限全屏非activity情况)
*
* @param window 当前窗口
* @param color 颜色
*/
public static void setStatusBarDarkIcon(Window window, int color) {
try {
setStatusBarColor(window, color);
if (Build.VERSION.SDK_INT > 22) {
setStatusBarDarkIcon(window.getDecorView(), true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置状态栏字体图标颜色
*
* @param activity 当前activity
* @param dark 是否深色 true为深色 false 为白色
*/
public static void setStatusBarDarkIcon(Activity activity, boolean dark) {
setStatusBarDarkIcon(activity, dark, true);
}
private static boolean changeMeizuFlag(WindowManager.LayoutParams winParams, String flagName, boolean on) {
try {
Field f = winParams.getClass().getDeclaredField(flagName);
f.setAccessible(true);
int bits = f.getInt(winParams);
Field f2 = winParams.getClass().getDeclaredField("meizuFlags");
f2.setAccessible(true);
int meizuFlags = f2.getInt(winParams);
int oldFlags = meizuFlags;
if (on) {
meizuFlags |= bits;
} else {
meizuFlags &= ~bits;
}
if (oldFlags != meizuFlags) {
f2.setInt(winParams, meizuFlags);
return true;
}
} catch (Throwable e) {
e.printStackTrace();
}
return false;
}
/**
* 设置状态栏颜色
*
*/
private static void setStatusBarDarkIcon(View view, boolean dark) {
int oldVis = view.getSystemUiVisibility();
int newVis = oldVis;
if (dark) {
newVis |= SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
newVis &= ~SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
if (newVis != oldVis) {
view.setSystemUiVisibility(newVis);
}
}
/**
* 设置状态栏颜色
*
*/
private static void setStatusBarColor(Window window, int color) {
WindowManager.LayoutParams winParams = window.getAttributes();
if (mStatusBarColorFiled != null) {
try {
int oldColor = mStatusBarColorFiled.getInt(winParams);
if (oldColor != color) {
mStatusBarColorFiled.set(winParams, color);
window.setAttributes(winParams);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 设置状态栏字体图标颜色(只限全屏非activity情况)
*
* @param window 当前窗口
* @param dark 是否深色 true为深色 false 为白色
*/
public static void setStatusBarDarkIcon(Window window, boolean dark) {
if (Build.VERSION.SDK_INT < 23) {
changeMeizuFlag(window.getAttributes(), "MEIZU_FLAG_DARK_STATUS_BAR_ICON", dark);
} else {
View decorView = window.getDecorView();
setStatusBarDarkIcon(decorView, dark);
setStatusBarColor(window, 0);
}
}
private static void setStatusBarDarkIcon(Activity activity, boolean dark, boolean flag) {
if (mSetStatusBarDarkIcon != null) {
try {
mSetStatusBarDarkIcon.invoke(activity, dark);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} else {
if (flag) {
setStatusBarDarkIcon(activity.getWindow(), dark);
}
}
}
}
package com.ydl.ydlcommon.utils.statusBar;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import androidx.core.content.ContextCompat;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 设置状态栏
*/
public class StatusBarUtil {
private StatusBarUtil() {
}
/**
* 设置状态栏颜色
*
* @param activity 需要设置的activity
* @param color 状态栏颜色值
*/
public static void setColor(Activity activity, int color) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置状态栏透明
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// 生成一个状态栏大小的矩形
View statusView = createStatusView(activity, color);
// 添加 statusView 到布局中
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
decorView.addView(statusView);
// 设置根布局的参数
ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
rootView.setFitsSystemWindows(true);
rootView.setClipToPadding(true);
// }
}
/**
* 生成一个和状态栏大小相同的矩形条
*
* @param activity 需要设置的activity
* @param color 状态栏颜色值
* @return 状态栏矩形条
*/
private static View createStatusView(Activity activity, int color) {
// 获得状态栏高度
int statusBarHeight = getStatusBarHeight(activity);
// 绘制一个和状态栏一样高的矩形
View statusView = new View(activity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
statusBarHeight);
statusView.setLayoutParams(params);
statusView.setBackgroundColor(color);
return statusView;
}
public static int getStatusBarHeight(Context context) {
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
return context.getResources().getDimensionPixelSize(resourceId);
}
/**
* 修改状态栏为全透明
*/
public static void setTranslucent(Window window) {
if (null == window) return;
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
else
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
/**
* 修改状态栏颜色,支持4.4以上版本
*/
public static void setStatusBarColor(Activity activity, int colorId) {
Window window = activity.getWindow();
if (window == null) return;
window.setStatusBarColor(ContextCompat.getColor(activity, colorId));
}
/**
* 状态栏暗色模式,设置状态栏黑色文字、图标,
* 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
*
* @return 1:MIUUI 2:Flyme 3:android6.0
*/
public static int statusBarDarkMode(Activity activity) {
int result = 0;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (mIUISetStatusBarLightMode(activity.getWindow(), true)) {
result = 1;
} else if (isFlyme()) {
FlymeStatusbarUtils.setStatusBarDarkIcon(activity, true);
result = 2;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setAndroidNativeLightStatusBar(activity.getWindow(), true);
result = 3;
}
// }
return result;
}
/**
* 状态栏亮色模式,设置状态栏白色文字、图标,
* 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
*
* @return 1:MIUUI 2:Flyme 3:android6.0w
*/
public static int statusBarLightMode(Activity activity) {
int result = 0;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (mIUISetStatusBarLightMode(activity.getWindow(), false)) {
result = 1;
} else if (isFlyme()) {
FlymeStatusbarUtils.setStatusBarDarkIcon(activity, false);
result = 2;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setAndroidNativeLightStatusBar(activity.getWindow(), false);
result = 3;
}
// }
return result;
}
/**
* 判断是否为魅族手机
*/
private static boolean isFlyme() {
return "Meizu".equalsIgnoreCase(Build.BRAND);
}
/**
* 状态栏暗色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标
*/
public static void statusBarDarkMode(Activity activity, int type) {
if (type == 1) {
mIUISetStatusBarLightMode(activity.getWindow(), true);
} else if (type == 2) {
FlymeStatusbarUtils.setStatusBarDarkIcon(activity, true);
} else if (type == 3) {
setAndroidNativeLightStatusBar(activity.getWindow(), true);
}
}
/**
* 状态栏亮色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标
*/
public static void statusBarLightMode(Activity activity, int type) {
if (type == 1) {
mIUISetStatusBarLightMode(activity.getWindow(), false);
} else if (type == 2) {
FlymeStatusbarUtils.setStatusBarDarkIcon(activity, false);
} else if (type == 3) {
setAndroidNativeLightStatusBar(activity.getWindow(), false);
}
}
/**
* 需要MIUIV6以上
*
* @param dark 是否把状态栏文字及图标颜色设置为深色
* @return boolean 成功执行返回true
*/
private static boolean mIUISetStatusBarLightMode(Window window, boolean dark) {
boolean result = false;
if (window != null) {
Class<?> clazz = window.getClass();
try {
int darkModeFlag;
Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) {
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体
} else {
extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
}
result = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
if (dark) {
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
private static void setAndroidNativeLightStatusBar(Window window, boolean dark) {
View decor = window.getDecorView();
if (dark) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
else
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
}
......@@ -292,7 +292,9 @@ class YDLShareDialog : DialogFragment {
}
tv_save_pic.setOnClickListener {
ImageUtil.savePicture(mActivity, base64DataStr)
mActivity?.let {
ImageUtil.savePicture(it, base64DataStr)
}
}
}
......
......@@ -2,7 +2,6 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromYDelta="0"
android:toYDelta="100%p"/>
</set>
\ No newline at end of file
......@@ -3,7 +3,6 @@
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromYDelta="100%p"
android:toYDelta="0"/>
</set>
\ No newline at end of file
......@@ -123,4 +123,13 @@
<!--次级按钮字体颜色,和背景冲突时使用,黑色基本不变-->
<color name="platform_but_text_color_specific">#222222</color>
<color name="color_FAFAFB">#FAFAFB</color>
<color name="color_1C1F28">#1C1F28</color>
<color name="color_1DA1F2">#1DA1F2</color>
<color name="color_EB892C">#EB892C</color>
<color name="color_9D9EA7">#9D9EA7</color>
<color name="color_EFEFF1">#EFEFF1</color>
<color name="color_FAFAFF">#FAFAFF</color>
<color name="color_242424">#242424</color>
</resources>
......@@ -55,7 +55,7 @@
<item name="android:layout_height">0.5dp</item>
</style>
<style name="platform_BottomDialogAnimation">
<style name="platform_BottomDialogAnimation" parent="baseDialog">
<item name="android:windowEnterAnimation">@anim/platform_bottom_dialog_slide_show</item>
<item name="android:windowExitAnimation">@anim/platform_bottom_dialog_slide_hide</item>
</style>
......@@ -70,5 +70,19 @@
<item name="colorAccent">@color/platform_main_theme</item>
</style>
<style name="baseDialog" parent="android:style/Theme.Dialog">
<item name="android:windowNoTitle">true</item><!-- 是否显示title -->
<item name="android:windowFrame">@null</item><!-- Dialog的windowFrame框 -->
<item name="android:windowIsFloating">true</item><!-- 是否浮现在activity之上 -->
<item name="android:windowBackground">@android:color/transparent</item><!--背景透明-->
<item name="android:background">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<!-- 用来控制灰度的值,当为1时,界面除了我们的dialog内容是高亮显示的,dialog以外的区域是黑色的,完全看不到其他内容,系统的默认值是0.5 -->
<item name="android:backgroundDimAmount">0.6</item>
<item name="android:windowMinWidthMajor">50%</item>
<item name="android:windowMinWidthMinor">50%</item>
</style>
<style name="bottomUpDialog" parent="baseDialog">
<item name="android:windowAnimationStyle">@style/platform_BottomDialogAnimation</item>
</style>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment