Commit fd5ab45f by 万齐军

feat: 倾诉视频页

parent 5cf363dd
......@@ -29,7 +29,7 @@ class VideoShowAdapter(private val data: List<ConfideHomeBodyBean>?, private val
override fun onBindViewHolder(holder: BindingViewHolder<ItemVideoShowBinding>, position: Int) {
val itemVideoShowViewModel = dataList[position]
holder.binding.item = itemVideoShowViewModel
holder.itemView.setOnClickListener { /*event.videoShowClick(position, data)*/ }
holder.itemView.setOnClickListener { event.videoShowClick(position, data) }
}
override fun getItemCount() = dataList.size
......
......@@ -4,12 +4,12 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.opengl.Visibility
import android.os.Build
import android.text.TextUtils
import android.view.*
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.android.arouter.launcher.ARouter
......@@ -253,6 +253,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
pauseVoice()
ARouter.getInstance().build(ConfideRoute.R_VIDEO_SHOW).withInt("initPos", index)
.withString("initData", dataJson).navigation()
}
......
......@@ -38,4 +38,11 @@ interface ConfideHomeApi {
// 1=在线 3-通话中 2-离线
@GET("auth/listen/dialchangestatus")
fun getDialStatus(@Query("doctorId") doctorId: String): Observable<BaseAPIResponse<DialStatus>>
@GET
fun recommendDoctor(
@Url url: String,
@Query("page") page: Int,
@Query("businessSource") source: Int
): Observable<BaseAPIResponse<ConfideHomeDataBean>>
}
\ No newline at end of file
......@@ -15,28 +15,44 @@ import com.ydl.confide.R
import com.ydl.confide.api.ConfideRoute
import com.ydl.confide.databinding.ActivityExpertIntroBinding
import com.ydl.confide.home.bean.ConfideHomeBodyBean
import com.ydl.confide.home.bean.ConfideHomeDataBean
import com.ydl.confide.home.util.ConfideNetworkUtil
import com.ydl.confide.home.http.ConfideHomeApi
import com.ydl.confide.router.PhoneCallIn
import com.ydl.webview.H5Params
import com.ydl.webview.NewH5Activity
import com.ydl.ydlcommon.base.config.HttpConfig
import com.ydl.ydlcommon.base.config.HttpConfig.Companion.YDL_H5
import com.ydl.ydlcommon.utils.BuryPointUtils
import com.ydl.ydlcommon.view.dialog.CommonDialog
import com.ydl.ydlcommon.utils.LogUtil
import com.ydl.ydlnet.YDLHttpUtils
import com.yidianling.common.tools.ToastUtil
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
@Route(path = ConfideRoute.R_VIDEO_SHOW)
class ExpertIntroActivity : AppCompatActivity() {
companion object {
private const val SOURCE_VIDEO = 10
}
private val tag = javaClass.simpleName
@Autowired
@JvmField
var initPos: Int = 0
@Autowired
@JvmField
var initData:String?=null
var initData: String? = null
private var lastSelectPos = 0
private lateinit var binding: ActivityExpertIntroBinding
private lateinit var adapter: IntroAdapter
private var page = 1
private var disposable: Disposable? = null
private val confideApi = YDLHttpUtils.obtainApi(ConfideHomeApi::class.java)
private val data = mutableListOf<VideoViewModel>()
......@@ -51,14 +67,14 @@ class ExpertIntroActivity : AppCompatActivity() {
decorView.systemUiVisibility = option
window.statusBarColor = Color.TRANSPARENT
}
if(initData!=null){
if (initData != null) {
val beans = JSON.parseArray(initData, ConfideHomeBodyBean::class.java)
val vms = beans.map { VideoViewModel().mapOf(it) }
data.addAll(vms)
}
binding.ivBack.setOnClickListener { onBackPressed() }
binding.tvConfideRecord.setOnClickListener {
if (!PhoneCallIn.loginByOneKeyLogin(this,true)) {
if (!PhoneCallIn.loginByOneKeyLogin(this, true)) {
return@setOnClickListener
}
BuryPointUtils.getInstance().createMap()
......@@ -84,11 +100,28 @@ class ExpertIntroActivity : AppCompatActivity() {
}
private fun loadMore() {
// data.add(VideoViewModel())
// data.add(VideoViewModel())
// data.add(VideoViewModel())
// data.add(VideoViewModel())
// data.add(VideoViewModel())
// adapter.notifyItemRangeInserted(data.size - 5, 5)
disposable = confideApi.recommendDoctor(HttpConfig.JAVA_BASE_URL + "auth/listen/nsearch?", page, SOURCE_VIDEO)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ resp ->
if (resp.code == "200") {
page++
val body = resp.data.body
val map = body?.map { VideoViewModel().mapOf(it) }
map?.let {
data.addAll(it)
adapter.notifyItemRangeInserted(data.size - it.size, it.size)
}
} else {
ToastUtil.toastShort(resp.msg)
}
}, { throwable ->
LogUtil.e(tag, throwable.message)
})
}
override fun onDestroy() {
super.onDestroy()
disposable?.dispose()
}
}
\ No newline at end of file
......@@ -3,7 +3,6 @@ package com.ydl.confide.intro
import android.app.Activity
import android.content.Context
import android.net.Uri
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
......@@ -49,8 +48,7 @@ internal class IntroAdapter(
parent,
false
)
val holder = ItemIntroHolder(binding)
return holder
return ItemIntroHolder(binding)
}
override fun onBindViewHolder(holder: ItemIntroHolder, position: Int) {
......@@ -63,10 +61,13 @@ internal class IntroAdapter(
override fun onViewAttachedToWindow(holder: ItemIntroHolder) {
val adapterPosition = holder.adapterPosition
val videoView = IjkVideoView(context)
if (hasAgreePlayWithoutWiFi || ConfideNetworkUtil.isWifi(context)) {
videoView.setVideoURI(Uri.parse("https://video.ydlcdn.com/2020/04/01/ac2e4bb4e3ac8e2f0eca41e2d49c8484.mp4"))
} else {
data[adapterPosition].playUrl?.let { videoView.tag = it }
val playUrl = data[adapterPosition].playUrl
if (!playUrl.isNullOrBlank()) {
if (hasAgreePlayWithoutWiFi || ConfideNetworkUtil.isWifi(context)) {
videoView.setVideoURI(Uri.parse(playUrl))
} else {
videoView.tag = playUrl
}
}
videoViews.put(adapterPosition, videoView)
holder.onAttach(videoView)
......@@ -81,8 +82,9 @@ internal class IntroAdapter(
hasAgreePlayWithoutWiFi = true
for (entry in videoViews.entries) {
val value = entry.value
(value.tag as? String)?.let {
value.setVideoURI(Uri.parse(it))
val playUrl = value.tag as? String
if (!playUrl.isNullOrBlank()) {
value.setVideoURI(Uri.parse(playUrl))
if (curPos == entry.key) {
value.start()
}
......@@ -140,6 +142,21 @@ internal class IntroAdapter(
fun onSelect(position: Int) {
curPos = position
onLoadDialStatus(position)
if (!ConfideNetworkUtil.isWifi(context) && !hasAgreePlayWithoutWiFi) {
return
}
for (entry in videoViews.entries) {
if (entry.key == position) {
entry.value.seekTo(0)
entry.value.start()
} else {
entry.value.pause()
}
}
}
private fun onLoadDialStatus(position: Int) {
val confideApi = YDLHttpUtils.obtainApi(ConfideHomeApi::class.java)
val curUid = data[position].uid
if (curUid != null) {
......@@ -158,16 +175,6 @@ internal class IntroAdapter(
}
}, { throwable -> throwable.printStackTrace() })
}
if (!ConfideNetworkUtil.isWifi(context) && !hasAgreePlayWithoutWiFi) {
return
}
for (entry in videoViews.entries) {
if (entry.key == position) {
entry.value.seekTo(0)
entry.value.start()
} else {
entry.value.pause()
}
}
}
}
\ No newline at end of file
......@@ -12,6 +12,7 @@ import com.alibaba.android.arouter.launcher.ARouter
import com.dou361.ijkplayer.widget.IjkVideoView
import com.ydl.confide.databinding.ItemExpertIntroBinding
import com.ydl.confide.home.bean.ConfideHomeBodyBean
import com.ydl.ydlcommon.utils.LogUtil
import com.ydl.ydlcommon.view.dialog.YDLShareDialog
import com.yidianling.im.api.service.IImService
import io.reactivex.Observable
......@@ -29,6 +30,8 @@ internal class ItemIntroHolder(binding: ItemExpertIntroBinding) :
private var disposable: Disposable? = null
private var video: IjkVideoView? = null
private var vm: VideoViewModel? = null
@Volatile
private var isTouch = false
......@@ -38,6 +41,7 @@ internal class ItemIntroHolder(binding: ItemExpertIntroBinding) :
}
fun onBind(item: VideoViewModel) {
vm = item
binding.layoutCall.setOnClickListener { }
binding.btnChat.setOnClickListener {
val aty = it.context as? Activity
......@@ -55,12 +59,22 @@ internal class ItemIntroHolder(binding: ItemExpertIntroBinding) :
}
}
binding.videoView.setOnClickListener {
video?.pause()
binding.ivPlay.visibility = View.VISIBLE
if (video?.canPause() == true) {
video?.pause()
if (item.isVideo) {
binding.ivPlay.visibility = View.VISIBLE
} else {
binding.voicePlay.stop()
}
}
}
binding.ivPlay.setOnClickListener {
video?.start()
binding.ivPlay.visibility = View.GONE
if (item.isVideo) {
binding.ivPlay.visibility = View.GONE
} else {
binding.voicePlay.start()
}
}
binding.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
......@@ -80,6 +94,7 @@ internal class ItemIntroHolder(binding: ItemExpertIntroBinding) :
}
}
})
binding.voicePlay.visibility = if (item.isVideo) View.GONE else View.VISIBLE
}
fun onAttach(videoView: IjkVideoView) {
......@@ -95,22 +110,36 @@ internal class ItemIntroHolder(binding: ItemExpertIntroBinding) :
}
}
video?.setOnInfoListener { mp, what, extra ->
Log.d(TAG, "${what},${extra}")
return@setOnInfoListener false
Log.d(TAG, "OnInfo:${what},${extra}")
return@setOnInfoListener true
}
video?.setOnCompletionListener {
video?.seekTo(0)
video?.start()
}
video?.setOnErrorListener { player, what, extra ->
LogUtil.e(TAG, "onError:$what,$extra")
return@setOnErrorListener true
}
video?.setOnCompletionListener { stopTiming() }
startTiming()
if (binding.voicePlay.visibility == View.VISIBLE) {
binding.voicePlay.start()
}
}
private fun startTiming() {
disposable = Observable.interval(300, TimeUnit.MILLISECONDS)
disposable = Observable.interval(3, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation())
.filter { !isTouch }
.subscribe {
val pos = (video?.mMediaPlayer as IjkMediaPlayer?)?.currentPosition ?: 0
if (pos > 0) {
binding.seekbar.progress = pos.toInt()// video.bufferPercentage
if (vm?.isVideo == true) {
binding.ivCover.visibility = View.GONE
}
if (!isTouch) {
binding.seekbar.progress = pos.toInt()// video.bufferPercentage
}
}
}
}
......@@ -127,9 +156,9 @@ class VideoViewModel {
val lineStatus = ObservableInt()
val intro = ObservableField<String>("")
val tag = ObservableField<String>("")
var playUrl: String? = null//"https://video.ydlcdn.com/2020/04/01/ac2e4bb4e3ac8e2f0eca41e2d49c8484.mp4"
var playUrl: String? = null
var coverUrl = ObservableField<String>("")
var isVideo = true
var isVideo = false
var uid: String? = null
var linkUrl: String? = null
}
......@@ -141,7 +170,9 @@ internal fun VideoViewModel.mapOf(bean: ConfideHomeBodyBean): VideoViewModel {
intro.set(bean.confideContent)
val sb = StringBuilder()
bean.confidedTag?.forEach { sb.append(it).append(" | ") }
sb.setLength(sb.length - 1)
if (sb.isNotEmpty()) {
sb.setLength(sb.length - 3)
}
tag.set(sb.toString())
if (bean.videoUrl != null) {
playUrl = bean.videoUrl
......
......@@ -7,14 +7,14 @@
<shape>
<corners android:radius="4dp" />
<solid android:color="#59FFFFFF" />
<stroke android:width="1dp" />
<stroke android:width="1dp" android:color="@color/transparent"/>
</shape>
</item>
<item>
<shape>
<corners android:radius="4dp" />
<solid android:color="#59FFFFFF" />
<stroke android:width="2dp" />
<stroke android:width="3dp" android:color="@color/transparent"/>
</shape>
</item>
</selector>
......@@ -36,7 +36,7 @@
<shape>
<corners android:radius="4dp" />
<solid android:color="@color/white" />
<stroke android:width="1dp" />
<stroke android:width="1dp" android:color="@color/transparent"/>
</shape>
</clip>
</item>
......@@ -45,7 +45,7 @@
<shape>
<corners android:radius="4dp" />
<solid android:color="@color/white" />
<stroke android:width="2dp" />
<stroke android:width="3dp" android:color="@color/transparent"/>
</shape>
</clip>
</item>
......
......@@ -27,6 +27,16 @@
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/ivCover"
android:layout_width="0dp"
android:layout_height="0dp"
app:imageUrl="@{item.coverUrl}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/ivPlay"
android:layout_width="76dp"
android:layout_height="76dp"
......@@ -37,6 +47,18 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.yidianling.common.view.ui.VoicePlayingIcon
android:id="@+id/voicePlay"
android:layout_width="76dp"
android:layout_height="76dp"
android:padding="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shape="@{1}"
app:shapeBg="@{0x99000000}" />
<View
android:id="@+id/vDisableClick"
android:layout_width="0dp"
......@@ -117,14 +139,16 @@
<TextView
android:id="@+id/tvIntro"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="40dp"
android:layout_marginBottom="8dp"
android:text="@{item.intro}"
android:textColor="@color/white"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@+id/tvTag"
app:layout_constraintLeft_toLeftOf="@+id/tvTag"
app:layout_constraintRight_toLeftOf="@+id/btnShare"
tools:text="tag|tag|tag" />
<TextView
......@@ -143,8 +167,9 @@
android:id="@+id/tvConfideCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:layout_marginLeft="8dp"
android:text="@{item.count}"
android:textColor="@color/white"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@+id/tvName"
app:layout_constraintLeft_toRightOf="@+id/tvName"
......@@ -196,13 +221,18 @@
app:layout_constraintRight_toRightOf="parent" />
<ImageView
android:layout_width="46dp"
android:layout_height="46dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="20dp"
android:padding="1dp"
app:circle="@{true}"
app:imageUrl="@{item.avatar}"
app:layout_constraintBottom_toTopOf="@+id/btnChat"
app:layout_constraintRight_toRightOf="parent" />
app:layout_constraintRight_toRightOf="parent"
app:shape="@{1}"
app:shapeBg="@{0x00FFFFFF}"
app:shapeStrokeColor="@{0xFFFFFFFF}"
app:shapeStrokeWidth="@{1}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
......@@ -66,6 +66,7 @@ dependencies {
api(rootProject.ext.dependencies["support-v4"])
api(rootProject.ext.dependencies["appcompat-v7"])
api(rootProject.ext.dependencies["design"])
implementation(rootProject.ext.dependencies["annotations"])
implementation(rootProject.ext.dependencies["arouter"])
kapt(rootProject.ext.dependencies["arouter-compiler"])
compileOnly(rootProject.ext.dependencies["systembartint"])
......
package com.yidianling.common.view.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.yidianling.common.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class VoicePlayingIcon extends View {
//画笔
private Paint paint;
//跳动指针的集合
private List<Pointer> pointers;
//跳动指针的数量
private int pointerNum;
//逻辑坐标 原点
private float basePointX;
private float basePointY;
//指针间的间隙 默认5dp
private float pointerPadding;
//每个指针的宽度 默认3dp
private float pointerWidth;
//指针的颜色
private int pointerColor = Color.RED;
//控制开始/停止
private boolean isPlaying = false;
//子线程
private Thread myThread;
//指针波动速率
private int pointerSpeed;
private Random random;
private RectF rectF;
private float radius;
public VoicePlayingIcon(Context context) {
super(context);
init();
}
public VoicePlayingIcon(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//取出自定义属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.VoicePlayingIcon);
pointerColor = ta.getColor(R.styleable.VoicePlayingIcon_pointer_color, Color.WHITE);
pointerNum = ta.getInt(R.styleable.VoicePlayingIcon_pointer_num, 3);//指针的数量,默认为4
pointerWidth = dp2px(getContext(),
ta.getFloat(R.styleable.VoicePlayingIcon_pointer_width, 6f));//指针的宽度,默认5dp
pointerSpeed = ta.getInt(R.styleable.VoicePlayingIcon_pointer_speed, 40);
ta.recycle();
init();
}
public VoicePlayingIcon(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.VoicePlayingIcon);
pointerColor = ta.getColor(R.styleable.VoicePlayingIcon_pointer_color, Color.RED);
pointerNum = ta.getInt(R.styleable.VoicePlayingIcon_pointer_num, 3);
pointerWidth = dp2px(getContext(), ta.getFloat(R.styleable.VoicePlayingIcon_pointer_width, 6f));
pointerSpeed = ta.getInt(R.styleable.VoicePlayingIcon_pointer_speed, 40);
ta.recycle();
init();
}
/**
* 初始化画笔与指针的集合
*/
private void init() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(pointerColor);
radius = pointerWidth / 2;
pointers = new ArrayList<>();
for (int i = 0; i < pointerNum; i++) {
pointers.add(new Pointer());
}
random = new Random();
rectF = new RectF();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//获取逻辑原点的,也就是画布左下角的坐标。这里减去了paddingBottom的距离
basePointY = getHeight() - getPaddingBottom();
for (int i = 0; i < pointerNum; i++) {
//创建指针对象,利用0~1的随机数 乘以 可绘制区域的高度。作为每个指针的初始高度。
Pointer pointer = pointers.get(i);
if (pointer != null) {
pointer.height = (float) (0.1 * (random.nextInt(10) + 1) * (getHeight() - getPaddingBottom() - getPaddingTop()));
}
}
//计算每个指针之间的间隔 总宽度 - 左右两边的padding - 所有指针占去的宽度 然后再除以间隔的数量
pointerPadding = (getWidth() - getPaddingLeft() - getPaddingRight() - pointerWidth * pointerNum) / (pointerNum - 1);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将x坐标移动到逻辑原点,也就是左下角
basePointX = 0f + getPaddingLeft();
//循环绘制每一个指针。
for (int i = 0; i < pointers.size(); i++) {
//绘制指针,也就是绘制矩形
rectF.set(basePointX,
basePointY - pointers.get(i).height,
basePointX + pointerWidth,
basePointY);
canvas.drawRoundRect(rectF, radius, radius, paint);
basePointX += (pointerPadding + pointerWidth);
}
}
/**
* 开始播放
*/
public void start() {
if (!isPlaying) {
if (myThread == null) {//开启子线程
myThread = new Thread(new MyRunnable());
myThread.start();
}
isPlaying = true;//控制子线程中的循环
}
}
/**
* 停止子线程,并刷新画布
*/
public void stop() {
isPlaying = false;
invalidate();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (myThread != null) {
myThread.interrupt();
myThread = null;
}
}
/**
* 子线程,循环改变每个指针的高度
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (float i = 0; i < Integer.MAX_VALUE; ) {//创建一个死循环,每循环一次i+0.1
try {
for (int j = 0; j < pointers.size(); j++) { //循环改变每个指针高度
float rate = (float) Math.abs(Math.sin(i + j));//利用正弦有规律的获取0~1的数。
pointers.get(j).height = (basePointY - getPaddingTop()) * rate; //rate 乘以 可绘制高度,来改变每个指针的高度
}
Thread.sleep(pointerSpeed);//休眠一下下,可自行调节
if (isPlaying) { //控制开始/暂停
postInvalidate();
i += 0.1;
}
} catch (InterruptedException e) {
//ignore
break;
}
}
}
}
/**
* 指针对象
*/
private static class Pointer {
private float height;
}
static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}
......@@ -318,4 +318,11 @@
<attr name="ratingTitleVisible" format="boolean"/>
<attr name="ratingCenterColor" format="color"/>
</declare-styleable>
<declare-styleable name="VoicePlayingIcon">
<attr name="pointer_color" format="color" />
<attr name="pointer_num" format="integer" />
<attr name="pointer_width" format="float" />
<attr name="pointer_speed" format="integer" />
</declare-styleable>
</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