横屏显示view HScreenController.kt
class HScreenController(context: Context, attrs: AttributeSet? = null) :BaseWidget(context,attrs) {
var mSeekBar:MarkSeekBar?=null
var iv_pause: ImageView?=null
var mBack: ImageView?=null
var mCurTime: TextView?=null
var mTitle: TextView?=null
var mEndTime: TextView?=null
var ivHalfScreen:ImageView?=null
var toHalfScreen:() -> Unit={}
var pauseOrPlayClick:()->Unit={}
var seekTo:(position:Int)->Unit={}
var mTotalTime:Int=0
var isDragStart = false
override fun initView() {
mBack=view?.findViewById(R.id.mBack)
mTitle=view?.findViewById(R.id.mTitle)
mSeekBar=view?.findViewById(R.id.sb_progress)
iv_pause=view?.findViewById(R.id.iv_pause)
mCurTime=view?.findViewById(R.id.tv_cur_time)
mEndTime=view?.findViewById(R.id.tv_end_time)
ivHalfScreen=view?.findViewById(R.id.ivHalfScreen)
}
override fun initInnerEvent() {
mBack?.setOnClickListener {
toHalfScreen()
}
iv_pause?.setOnClickListener {
pauseOrPlayClick()
}
ivHalfScreen?.setOnClickListener{
toHalfScreen()
}
mSeekBar?.setOnProgressChangedListener(SeekBarChangeEvent())
}
inner class SeekBarChangeEvent : MarkSeekBar.OnProgressChangedListener {
override fun onProgressChanged(
signSeekBar: MarkSeekBar,
progress: Float,
fromUser: Boolean
) {
isDragStart = true
mCurTime?.text = DKTimeFormatter.getInstance().stringForTime(progress.toInt())
}
override fun getProgressOnActionUp(signSeekBar: MarkSeekBar, progress: Float) {
seekTo(progress.toInt())
if (mSeekBar?.isPressed ==true) {
mSeekBar?.isPressed = false
}
isDragStart = false
}
}
fun setTitle(title:String?){
mTitle?.text=title?:""
}
fun setTotalTime(totalTime:Int){
mTotalTime = totalTime
mSeekBar?.max=totalTime.toFloat()
mEndTime?.text = DKTimeFormatter.getInstance().stringForTime(mTotalTime)
}
fun updateProgress(position:Int) {
mCurTime?.text = DKTimeFormatter.getInstance().stringForTime(position)
if (mSeekBar != null && mTotalTime > 0) {
mSeekBar?.progress = position.toFloat()
}
}
fun updateSecondCache(cacheTime: Int) {
if (mSeekBar != null && mTotalTime > 0) {
mSeekBar?.setCacheProgress((cacheTime * 1000).toFloat())
}
}
fun showPlaying(){
iv_pause?.setImageResource(R.drawable.player_play_start)
}
fun showPause(){
iv_pause?.setImageResource(R.drawable.player_pause_16)
}
fun showPlayEnd(){
mSeekBar?.progress = mSeekBar?.max ?:100.toFloat()
mCurTime?.text = DKTimeFormatter.getInstance().stringForTime(mTotalTime)
mEndTime?.text = DKTimeFormatter.getInstance().stringForTime(mTotalTime)
}
override fun getLayoutId() = R.layout.player_h_controller
}
横屏显示view布局 player_h_controller.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="187.5dp"
android:background="@drawable/bg_00000000_66000000">
<LinearLayout
android:orientation="horizontal"
android:layout_marginHorizontal="20dp"
android:gravity="center_vertical"
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/mBack"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:background="@drawable/icon_player_back" />
<TextView
android:id="@+id/mTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_marginLeft="22dp"
tools:text="雅思词汇分级及时间分配"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@android:color/white"
android:textSize="14dp" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/media_controller_h"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:paddingTop="12dp"
android:gravity="center_vertical"
android:layout_alignParentBottom="true"
android:background="@drawable/bg_00000000_66000000_270">
<ImageView
android:id="@+id/iv_pause"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="5dp"
android:layout_marginLeft="22dp"
android:src="@drawable/icon_player_pause_xxx" />
<TextView
android:id="@+id/tv_cur_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:layout_marginLeft="7dp"
android:text="00:00"
android:textColor="@color/white"
android:textSize="11dp" />
<TextView
android:id="@+id/tv_sep_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/"
android:textStyle="bold"
android:textColor="@color/white"
android:textSize="11dp" />
<TextView
android:id="@+id/tv_end_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textStyle="bold"
android:textColor="@color/white"
android:textSize="11dp" />
<RelativeLayout
android:id="@+id/player_seek_rl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal">
<com.koo.ieltspro2019.view.MarkSeekBar
android:id="@+id/sb_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
app:msb_cache_track_color="#CCFFFFFF"
app:msb_cache_track_size="2dp"
app:msb_mark_radius="2.5dp"
app:msb_min="0"
app:msb_second_track_color="#FC5D64"
app:msb_second_track_size="2dp"
app:msb_section_mark_color="#FFFFFF"
app:msb_show_section_mark="true"
app:msb_show_thumb_shadow="true"
app:msb_thumb_radius="10dp"
app:msb_thumb_radius_on_dragging="13dp"
app:msb_touch_to_seek="true"
app:msb_track_color="#66FFFFFF"
app:msb_track_size="2dp" />
</RelativeLayout>
<ImageView
android:id="@+id/ivHalfScreen"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginRight="19dp"
android:padding="5dp"
android:src="@drawable/player_icon_full"/>
</LinearLayout>
</RelativeLayout>
加载中view PlayerLoadingView.kt
class PlayerLoadingView(context: Context, attrs: AttributeSet? = null) :BaseWidget(context, attrs){
private var mLoadinLl: LinearLayout? = null
private var mLvLoading: ImageView? = null
private var mTvLoadingText: TextView? = null
private var mTvLoadingSpeed: TextView? = null
private var hyperspaceJumpAnimation: Animation? = null
override fun initView() {
mLoadinLl = view?.findViewById(R.id.ll_loading_common)
mLvLoading = view?.findViewById(R.id.lv_loading)
mTvLoadingSpeed = view?.findViewById(R.id.tv_loading_speed)
mTvLoadingText = view?.findViewById(R.id.tv_loading_text)
hyperspaceJumpAnimation = AnimationUtils.loadAnimation(context,R.anim.loading_animation)
}
override fun initInnerEvent() {
}
override fun getLayoutId()= R.layout.player_loading_view
fun showBuffering() {
visibility = VISIBLE
mLvLoading?.visibility = VISIBLE
mTvLoadingSpeed?.visibility = VISIBLE
mTvLoadingText?.visibility = GONE
mLoadinLl?.visibility = VISIBLE
mLvLoading?.startAnimation(hyperspaceJumpAnimation)
}
fun showPreLoading() {
visibility = VISIBLE
mLvLoading?.visibility = VISIBLE
mTvLoadingSpeed?.visibility = GONE
mTvLoadingText?.visibility = GONE
mLoadinLl?.visibility = VISIBLE
mLvLoading?.startAnimation(hyperspaceJumpAnimation)
}
fun updateLoadingSpeed(what: Int, extras: Int) {
if (what == KoolMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH && mTvLoadingSpeed != null && mTvLoadingSpeed?.visibility == VISIBLE) {
mTvLoadingSpeed?.text = context.resources.getString(R.string.player_loading_speed, extras)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mLvLoading?.clearAnimation()
}
}
加载中view布局 player_loading_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_loading_common"
android:layout_width="90dp"
android:layout_height="73dp"
android:gravity="center"
android:orientation="vertical"
android:background="@drawable/bg_cc212538_radius_10"
android:visibility="gone">
<ImageView
android:id="@+id/lv_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/ic_loading"/>
<TextView
android:id="@+id/tv_loading_speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingBottom="1dp"
android:text="0 KB/s"
android:textColor="#ccffffff"
android:textSize="12dp"
android:visibility="gone" />
<TextView
android:id="@+id/tv_loading_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="加载中(0%)"
android:textColor="#ccffffff"
android:textSize="10dp"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
手指左右滑动屏幕时 显示的进度view SeekingMonitorView.kt
class SeekingMonitorView(context: Context, attrs: AttributeSet? = null) :BaseWidget(context,attrs) {
private var mSeekBar: SeekBar? = null
private var mCurrentTimeTv: TextView? = null
private var startX = 0f
private var mDuration:Int?=1
private var mProgress: Int?=0
var seekTo:(position:Int)->Unit={}
var currentPosition:Int?=0
override fun initView() {
mSeekBar = view?.findViewById(R.id.monitorSeekBar)
mCurrentTimeTv = view?.findViewById(R.id.dragTimeTv)
}
override fun initInnerEvent() {
}
override fun getLayoutId() = R.layout.player_seek_monitor_view
fun initMonitorView(progress: Int,duration: Int) {
mProgress=progress
mDuration=duration
if (mSeekBar != null) {
mSeekBar?.max = duration
mSeekBar?.progress = progress
}
if (mCurrentTimeTv != null) {
mCurrentTimeTv!!.text = TimeUtils.parseShortTime(progress)
}
}
fun seekStart(touchX:Float){
if (startX==-1f){
startX=touchX
}else{
var tempX=touchX-startX
if(abs(tempX)>5){
val percentProgress = getStepPosition(tempX)
currentPosition = getSeekToPosition(percentProgress, mProgress?:0, mDuration?:0)
updatePositionText(currentPosition?:0)
if (mSeekBar != null) {
mSeekBar?.max = mDuration?:100
mSeekBar?.progress = currentPosition?:0 * 100 / if(mDuration!=null&&mDuration!!>0){mDuration!!}else{1}
}
}
}
}
fun adjustSeekEnd() {
startX=-1f
seekTo(currentPosition?:0)
}
private fun getSeekToPosition(percentProgress: Int, currentPosition: Int, duration: Int): Int {
val tPosition = currentPosition + percentProgress*duration/100
if (tPosition < 0) {
return 0
} else if (tPosition > duration) {
return duration
}
return tPosition
}
private fun updatePositionText(updatePosition: Int) {
var updatePosition = updatePosition
if (updatePosition < 0) {
updatePosition = 0
}
val parseShortTime = TimeUtils.parseShortTime(updatePosition)
if (mCurrentTimeTv != null) {
mCurrentTimeTv?.text = parseShortTime
}
}
fun getStepPosition(tempX: Float): Int {
return (tempX.toInt()*100/context.resources.displayMetrics.widthPixels)
}
fun hideMonitorView() {
visibility = GONE
}
}
进度view的布局 player_seek_monitor_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_monitor_common"
android:layout_width="160dp"
android:background="@drawable/player_progress_center_bg"
android:orientation="vertical"
android:visibility="visible"
android:gravity="center"
android:layout_height="73dp">
<TextView
android:id="@+id/dragTimeTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/C_FFFFFFFF"
android:textSize="17dp"
android:text=""
android:layout_marginTop="10dp"
android:layout_gravity="center_horizontal"/>
<com.koo.ieltspro2019.player.ui.widget.PlayerSeekbar
android:id="@+id/monitorSeekBar"
android:layout_marginTop="9dp"
android:layout_marginBottom="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:maxHeight="1dp"
android:minHeight="1dp"
android:padding="0dp"
android:paddingEnd="0dp"
android:paddingStart="0dp"
android:progressDrawable="@drawable/player_seekbar"
android:thumb="@null"/>
</LinearLayout>
</LinearLayout>
调节音量和亮度的父类 GestureView.java
public abstract class GestureView extends FrameLayout {
private Animator mAnimator = null;
private Runnable mDissmissRunnable = new Runnable() {
@Override
public void run() {
hide();
}
};
protected ImageView mIcon;
private boolean mIsShowing;
protected TextView mText;
protected abstract int getIcon();
protected abstract int getTextMarginTop();
public GestureView(Context context) {
super(context);
init();
}
private void init() {
Context context = getContext();
mIcon = new ImageView(context);
LayoutParams layoutParams = new LayoutParams(Utils.dp2px(76), Utils.dp2px(93));
layoutParams.gravity = Gravity.CENTER;
mIcon.setLayoutParams(layoutParams);
mIcon.setImageResource(getIcon());
addView(mIcon);
mText = new TextView(context);
LayoutParams layoutParams2 = new LayoutParams(-2, -2);
layoutParams2.gravity = 17;
layoutParams2.topMargin = getTextMarginTop();
mText.setLayoutParams(layoutParams2);
mText.setTextColor(0xFFFFFFFF);
mText.setTextSize(0, (float) Utils.dp2px(12));
addView(mText);
setVisibility(View.GONE);
}
public boolean isShowing() {
return mIsShowing;
}
public void triggerAutoDismiss() {
Scheduler.getUIHandler().removeCallbacks(mDissmissRunnable);
Scheduler.getUIHandler().postDelayed(mDissmissRunnable, 1800);
}
public final void show() {
if (!mIsShowing) {
animateIn();
mIsShowing = true;
}
triggerAutoDismiss();
}
public final void hide() {
if (mIsShowing) {
animateOut();
mIsShowing = false;
}
}
public final void gone() {
if (mIsShowing) {
setVisibility(View.GONE);
mIsShowing = false;
}
}
public void animateIn() {
if (mAnimator != null) {
mAnimator.end();
}
mAnimator = AnimatorFactory.animateAlphaIn(this);
}
public void animateOut() {
if (mAnimator != null) {
mAnimator.end();
}
mAnimator = AnimatorFactory.animateAlphaOut(this);
}
}
动画框架 AnimatorFactory.java
public class AnimatorFactory {
private static final int mDurationTime = 250
public static class AnimatorInvalidateUpdateListener implements ValueAnimator.AnimatorUpdateListener {
private View mView
public AnimatorInvalidateUpdateListener(View view) {
this.mView = view
}
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (this.mView != null) {
this.mView.invalidate()
}
}
}
private static int getHeight(View view) {
int height = view.getHeight()
if (height == 0) {
return view.getContext().getResources().getDisplayMetrics().heightPixels
}
return height
}
private static int getWidth(View view) {
int width = view.getWidth()
if (width == 0) {
return view.getContext().getResources().getDisplayMetrics().widthPixels
}
return width
}
public static ValueAnimator animateInBottomView(View view) {
ValueAnimator ofFloat = ObjectAnimator.ofFloat(view, "translationY", new float[]{(float) getHeight(view), 0.0f})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addUpdateListener(new AnimatorInvalidateUpdateListener(view))
ofFloat.start()
view.setVisibility(View.VISIBLE)
return ofFloat
}
public static ValueAnimator animateInTopView(View view) {
ValueAnimator ofFloat = ObjectAnimator.ofFloat(view, "translationY", new float[]{(float) (-getHeight(view)), 0.0f})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addUpdateListener(new AnimatorInvalidateUpdateListener(view))
ofFloat.start()
view.setVisibility(View.VISIBLE)
return ofFloat
}
public static ValueAnimator animateInLeftView(View view) {
ValueAnimator ofFloat = ObjectAnimator.ofFloat(view, "translationX", new float[]{(float) (-getWidth(view)), 0.0f})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addUpdateListener(new AnimatorInvalidateUpdateListener(view))
ofFloat.start()
view.setVisibility(View.VISIBLE)
return ofFloat
}
public static ValueAnimator animateInRightView(View view) {
ValueAnimator ofFloat = ObjectAnimator.ofFloat(view, "translationX", new float[]{(float) getWidth(view), 0.0f})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addUpdateListener(new AnimatorInvalidateUpdateListener(view))
view.setVisibility(View.VISIBLE)
ofFloat.start()
return ofFloat
}
public static Animator animateOutBottomView(View view) {
Animator ofFloat = ObjectAnimator.ofFloat(view, "translationY", new float[]{0.0f, (float) view.getHeight()})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addListener(new ViewGoneAnimatorListener(view))
ofFloat.start()
return ofFloat
}
public static Animator animateOutTopView(View view) {
Animator ofFloat = ObjectAnimator.ofFloat(view, "translationY", new float[]{0.0f, (float) (-view.getHeight())})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addListener(new ViewGoneAnimatorListener(view))
ofFloat.start()
return ofFloat
}
public static Animator animateOutLeftView(View view) {
Animator ofFloat = ObjectAnimator.ofFloat(view, "translationX", new float[]{0.0f, (float) (-getWidth(view))})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addListener(new ViewGoneAnimatorListener(view))
ofFloat.start()
return ofFloat
}
public static Animator animateOutRightView(View view) {
Animator ofFloat = ObjectAnimator.ofFloat(view, "translationX", new float[]{0.0f, (float) getWidth(view)})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addListener(new ViewGoneAnimatorListener(view))
ofFloat.start()
return ofFloat
}
public static Animator animateAlphaIn(View view) {
Animator ofFloat = ObjectAnimator.ofFloat(view, "alpha", new float[]{0.0f,0.9f})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.start()
view.setVisibility(View.VISIBLE)
return ofFloat
}
public static Animator animateAlphaOut(View view) {
Animator ofFloat = ObjectAnimator.ofFloat(view,"alpha", new float[]{0.9f, 0.0f})
ofFloat.setInterpolator(new DecelerateInterpolator())
ofFloat.setDuration(mDurationTime)
ofFloat.addListener(new ViewGoneAnimatorListener(view))
ofFloat.start()
return ofFloat
}
}
音量调节 GestureVolumn.java
public class GestureVolumn extends GestureView {
public GestureVolumn(Context context) {
super(context);
}
public static GestureVolumn create(RelativeLayout relativeLayout) {
GestureVolumn gestureVolumn = new GestureVolumn(relativeLayout.getContext());
gestureVolumn.setLayoutParams(new LayoutParams(-1, -1));
relativeLayout.addView(gestureVolumn);
return gestureVolumn;
}
public void adjustVolume(Activity activity, float f) {
int streamMaxVolume = 0;
int newVolumeValue = 0;
AudioManager audioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
streamMaxVolume = audioManager.getStreamMaxVolume(3);
newVolumeValue = getNewVolumeValue(f, streamMaxVolume, audioManager.getStreamVolume(3));
Log.d("VOLUME_audio","streamMaxVolume:"+streamMaxVolume+" newVolumeValue:"+newVolumeValue);
audioManager.setStreamVolume(3, newVolumeValue, 8);
if (streamMaxVolume != 0) {
setPercent((newVolumeValue * 100) / streamMaxVolume);
}
show();
}
private void setPercent(int i) {
this.mText.setText(i + "%");
}
private int getNewVolumeValue(float f, int i, int i2) {
int r0 = f > 0.0f ? i2 - 1 : f < 0.0f ? i2 + 1 : i2;
if (r0 > i) {
r0 = i;
}
if (r0 < 0) {
return 0;
}
return r0;
}
@Override
protected int getIcon() {
return R.drawable.player_icon_vol;
}
@Override
protected int getTextMarginTop() {
return Utils.dp2px(16);
}
}
亮度调节 GestureBrightness.java
public class GestureBrightness extends GestureView {
public GestureBrightness(Context context) {
super(context);
}
public static GestureBrightness create(RelativeLayout relativeLayout) {
GestureBrightness gestureBrightness = new GestureBrightness(relativeLayout.getContext());
gestureBrightness.setLayoutParams(new LayoutParams(-1, -1));
relativeLayout.addView(gestureBrightness);
return gestureBrightness;
}
public void adjustBrightness(Activity activity, float f) {
int activityBrightness = (int) (AndroidUtils.getActivityBrightness(activity) * 255.0f);
if (activityBrightness < 0) {
activityBrightness = AndroidUtils.getSystemBrightness(activity);
}
activityBrightness = getNewBrightnessValue(f, activityBrightness);
AndroidUtils.setActivityBrightness(activity, activityBrightness);
setPercent((activityBrightness * 100) / MotionEventCompat.ACTION_MASK);
show();
}
private void setPercent(int i) {
this.mText.setText(i + "%");
}
private int getNewBrightnessValue(float f, int i) {
int mask = MotionEventCompat.ACTION_MASK;
int r2 = f > 0.0f ? i - 17 : f < 0.0f ? i + 17 : i;
if (r2 <= MotionEventCompat.ACTION_MASK) {
mask = r2;
}
if (mask < 2) {
return 2;
}
return mask;
}
@Override
protected int getIcon() {
return R.drawable.player_icon_light;
}
@Override
protected int getTextMarginTop() {
return Utils.dp2px(16);
}
}
网络播放错误 PlayerNetErrorView.kt
class PlayerNetErrorView(context: Context, attrs: AttributeSet?):BaseWidget(context, attrs) {
private var mRetryBtn: TextView? = null
override fun initView() {
mRetryBtn = view?.findViewById(R.id.mRetryBtn)
}
override fun initInnerEvent() {
}
override fun getLayoutId()= R.layout.view_play_error
fun initListener(listener:OnClickListener?){
mRetryBtn?.setOnClickListener(listener)
}
}
播放错误布局 view_play_error.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#99000000"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/mErrorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Oops! 视频加载失败"
android:textColor="@android:color/white"
android:textSize="14dp"
android:textStyle="bold" />
<TextView
android:id="@+id/mRetryBtn"
android:layout_width="75dp"
android:layout_height="26dp"
android:layout_marginTop="20dp"
android:background="@drawable/bg_corner_13_66ffffff"
android:gravity="center"
android:text="点击重试"
android:textColor="@android:color/white"
android:textSize="12dp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
播放完成 PlayCompleteView.kt
class PlayCompleteView(context: Context, attrs: AttributeSet? = null) :BaseWidget(context, attrs) {
private var mTipsTitleTv: TextView? = null
private var mReplayTv: TextView? = null
private var mPlayNextTv: TextView? = null
var replay:()->Unit={}
var playNext:()->Unit={}
override fun initView() {
mTipsTitleTv = view?.findViewById(R.id.tipsTitleTv)
mPlayNextTv = view?.findViewById(R.id.playNextTv)
mReplayTv = view?.findViewById(R.id.replayTv)
}
override fun initInnerEvent() {
mReplayTv?.setOnClickListener {
replay()
}
mPlayNextTv?.setOnClickListener {
playNext()
}
}
fun showPlayCompleteView() {
visibility = VISIBLE
}
fun hideMonitorView() {
visibility = GONE
}
override fun getLayoutId()= R.layout.player_play_complete_portrait_view
}
播放完成 player_play_complete_portrait_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#96212538"
android:gravity="center"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/ll_monitor_common"
android:layout_width="200dp"
android:visibility="visible"
android:gravity="center"
android:layout_height="100dp">
<TextView
android:id="@+id/tipsTitleTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/C_FFFFFFFF"
android:layout_centerHorizontal="true"
android:textStyle="bold"
android:textSize="14dp"
android:text="本节学完啦~"/>
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_below="@id/tipsTitleTv"
android:layout_centerHorizontal="true"
android:gravity="center"
android:layout_marginTop="20dp"
android:layout_height="26dp">
<TextView
android:id="@+id/replayTv"
android:layout_width="wrap_content"
android:paddingHorizontal="14dp"
android:layout_height="match_parent"
android:textColor="@color/C_FFFFFFFF"
android:gravity="center"
android:background="@drawable/bg_40ffffff_14"
android:textStyle="bold"
android:text="重新播放"/>
<TextView
android:id="@+id/playNextTv"
android:layout_width="wrap_content"
android:paddingHorizontal="14dp"
android:layout_marginLeft="25dp"
android:gravity="center"
android:layout_height="match_parent"
android:textColor="@color/C_FFFFFFFF"
android:background="@drawable/bg_40ffffff_14"
android:textStyle="bold"
android:text="继续下节"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
播放器的PlayerSeekbar.java
public class MarkSeekBar extends View {
//progress最小值
private float mMin
// progress最大值
private float mMax
// 当前值
private float mProgress
//已缓存显示
private float mCacheProgress
// 进度条高度
private int mTrackSize
// 已选中进度条高度
private int mSecondTrackSize
//缓存条的高度size
private int mCacheTrackSize
// 滑动点,半径
private int mThumbRadius
//是否显示滑动点
private boolean isShowThumb
// 标记点,半径
private int mMarkRadius
// 拖动时的 滑动点半径
private int mThumbRadiusOnDragging
// 第一轨迹颜色
private int mTrackColor
// 第二轨迹颜色
private int mSecondTrackColor
//缓存轨迹颜色
private int mCacheTrackColor
//标记点颜色
private int mSectionMarkColor
// 滑动点的颜色
private int mThumbColor
// 是否显示节点标记
private boolean isShowSectionMark
private ArrayList<Float> mCustomArrayFloat
//动画持续时间
private long mAnimDuration
private boolean isTouchToSeek
private float mDelta
private float mThumbCenterX
private float mTrackLength
//每一份的宽度 ,假如(progress max是 999的话 mPerProgressWidth的值是,总宽度/999)
private float mPerProgressWidth
private boolean isThumbOnDragging
private OnProgressChangedListener mProgressListener
//轨道的左边距离本view的间距
private float mLeft
//轨道的右边距离本view的间距
private float mRight
private Paint mPaint
private Rect mRectText
private float mThumbBgAlpha
private float mThumbRatio
private boolean isShowThumbShadow
public MarkSeekBar(Context context) {
this(context, null)
}
public MarkSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0)
}
public MarkSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr)
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MarkSeekBar, defStyleAttr, 0)
mMin = a.getFloat(R.styleable.MarkSeekBar_msb_min, 0.0f)
mMax = a.getFloat(R.styleable.MarkSeekBar_msb_max, 100.0f)
mProgress = a.getFloat(R.styleable.MarkSeekBar_msb_progress, mMin)
mTrackSize = a.getDimensionPixelSize(R.styleable.MarkSeekBar_msb_track_size, dp2px(2))
mSecondTrackSize = a.getDimensionPixelSize(R.styleable.MarkSeekBar_msb_second_track_size, mTrackSize + dp2px(2))
mCacheTrackSize = a.getDimensionPixelSize(R.styleable.MarkSeekBar_msb_cache_track_size, mTrackSize + dp2px(2))
mThumbRadius = a.getDimensionPixelSize(R.styleable.MarkSeekBar_msb_thumb_radius, mSecondTrackSize + dp2px(2))
mMarkRadius = a.getDimensionPixelSize(R.styleable.MarkSeekBar_msb_mark_radius, 0)
mThumbRadiusOnDragging = a.getDimensionPixelSize(R.styleable.MarkSeekBar_msb_thumb_radius_on_dragging, mSecondTrackSize * 2)
mTrackColor = a.getColor(R.styleable.MarkSeekBar_msb_track_color, ContextCompat.getColor(context, R.color.colorPrimary))
mSecondTrackColor = a.getColor(R.styleable.MarkSeekBar_msb_second_track_color, ContextCompat.getColor(context, R.color.colorAccent))
mCacheTrackColor = a.getColor(R.styleable.MarkSeekBar_msb_cache_track_color, ContextCompat.getColor(context, R.color.colorAccent))
mThumbColor = a.getColor(R.styleable.MarkSeekBar_msb_thumb_color, mSecondTrackColor)
isShowSectionMark = a.getBoolean(R.styleable.MarkSeekBar_msb_show_section_mark, false)
isShowThumb = a.getBoolean(R.styleable.MarkSeekBar_msb_is_show_thumb, true)
mSectionMarkColor = a.getColor(R.styleable.MarkSeekBar_msb_section_mark_color, Color.WHITE)
int duration = a.getInteger(R.styleable.MarkSeekBar_msb_anim_duration, -1)
mAnimDuration = duration < 0 ? 200 : duration
isTouchToSeek = a.getBoolean(R.styleable.MarkSeekBar_msb_touch_to_seek, false)
mThumbBgAlpha = a.getFloat(R.styleable.MarkSeekBar_msb_thumb_bg_alpha, 0.2f)
mThumbRatio = a.getFloat(R.styleable.MarkSeekBar_msb_thumb_ratio, 0.7f)
isShowThumbShadow = a.getBoolean(R.styleable.MarkSeekBar_msb_show_thumb_shadow, false)
a.recycle()
mPaint = new Paint()
mPaint.setAntiAlias(true)
mPaint.setStrokeCap(Paint.Cap.ROUND)
mPaint.setTextAlign(Paint.Align.CENTER)
mRectText = new Rect()
initConfigByPriority()
}
private void initConfigByPriority() {
if (mMin == mMax) {
mMin = 0.0f
mMax = 100.0f
}
if (mMin > mMax) {
float tmp = mMax
mMax = mMin
mMin = tmp
}
if (mProgress < mMin) {
mProgress = mMin
}
if (mProgress > mMax) {
mProgress = mMax
}
if (mCacheProgress < mMin) {
mCacheProgress = mMin
}
if (mCacheProgress > mMax) {
mCacheProgress = mMax
}
if (mSecondTrackSize < mTrackSize) {
mSecondTrackSize = mTrackSize + dp2px(2)
}
if (mThumbRadius <= mSecondTrackSize) {
mThumbRadius = mSecondTrackSize + dp2px(2)
}
if (mMarkRadius == 0) {
mMarkRadius = mSecondTrackSize + dp2px(2)
}
if (mThumbRadiusOnDragging <= mSecondTrackSize) {
mThumbRadiusOnDragging = mSecondTrackSize * 2
}
mDelta = mMax - mMin
setProgress(mProgress)
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
int height = mThumbRadiusOnDragging * 2
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), height)
mLeft = getPaddingLeft() + mThumbRadiusOnDragging
mRight = getMeasuredWidth() - getPaddingRight() - mThumbRadiusOnDragging
//轨迹长度
mTrackLength = mRight - mLeft
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas)
float xLeft = getPaddingLeft()
float xRight = getMeasuredWidth() - getPaddingRight()
float yTop = getPaddingTop() + mThumbRadiusOnDragging
//轨迹长度/总size 得到的结果
mPerProgressWidth = mTrackLength * 1f / mMax
if (isShowThumb) {
xLeft += mThumbRadiusOnDragging
xRight -= mThumbRadiusOnDragging
}
if (!isThumbOnDragging) {
mThumbCenterX = mTrackLength / mDelta * (mProgress - mMin) + xLeft
}
// draw track
mPaint.setColor(mSecondTrackColor)
mPaint.setStrokeWidth(mSecondTrackSize)
canvas.drawLine(xLeft, yTop, mThumbCenterX, yTop, mPaint)
// draw second track
mPaint.setColor(mTrackColor)
mPaint.setStrokeWidth(mTrackSize)
canvas.drawLine(mThumbCenterX, yTop, xRight, yTop, mPaint)
//画缓存进度条
if (mCacheProgress * mPerProgressWidth > mThumbCenterX) {
mPaint.setColor(mCacheTrackColor)
mPaint.setStrokeWidth(mCacheTrackSize)
canvas.drawLine(mThumbCenterX, yTop, xLeft + mCacheProgress * mPerProgressWidth, yTop, mPaint)
}
//boolean conditionInterval = mSectionCount % 2 == 0
boolean conditionInterval = true
// draw sectionMark
if (isShowSectionMark && mCustomArrayFloat != null) {
drawCustomMark(canvas, xLeft, yTop, conditionInterval)
}
// draw thumb
mPaint.setColor(mThumbColor)
//draw thumb shadow
if (isShowThumbShadow && isShowThumb) {
canvas.drawCircle(mThumbCenterX, yTop, isThumbOnDragging ? mThumbRadiusOnDragging * mThumbRatio : mThumbRadius * mThumbRatio, mPaint)
mPaint.setColor(getColorWithAlpha(mThumbColor, mThumbBgAlpha))
}
//Paint paint = new Paint()
//Shader shader = new RadialGradient(mThumbCenterX, yTop, isThumbOnDragging ? mThumbRadiusOnDragging : mThumbRadius, mThumbColor, getColorWithAlpha(mThumbColor, mThumbBgAlpha), Shader.TileMode.CLAMP)
//paint.setShader(shader)
if (isShowThumb) {
canvas.drawCircle(mThumbCenterX, yTop, isThumbOnDragging ? mThumbRadiusOnDragging : mThumbRadius, mPaint)
}
}
/**
* 画自定义节点标记
*
* @param canvas
* @param xLeft
* @param yTop
* @param conditionInterval 条件区间,外界传过来的总是true
*/
private void drawCustomMark(Canvas canvas, float xLeft, float yTop, boolean conditionInterval) {
// 交汇点x轴坐标
float junction = mTrackLength / mDelta * Math.abs(mProgress - mMin) + mLeft
//设置文本画笔的文字大小及边框
mPaint.getTextBounds("0123456789", 0, "0123456789".length(), mRectText)
float x_
for (int i = 0
x_ = xLeft + mCustomArrayFloat.get(i) * mPerProgressWidth
mPaint.setColor(x_ <= junction ? mSecondTrackColor : mSectionMarkColor)
// sectionMark
canvas.drawCircle(x_, yTop, mMarkRadius, mPaint)
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh)
post(new Runnable() {
@Override
public void run() {
requestLayout()
}
})
}
float dx
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true)
isThumbOnDragging = isThumbTouched(event)
//拖动中
if (isThumbOnDragging) {
invalidate()
} else if (isTouchToSeek && isTrackTouched(event)) {
//非拖动中允许点击到指定位置的话则
isThumbOnDragging = true
mThumbCenterX = event.getX()
if (mThumbCenterX < mLeft) {
mThumbCenterX = mLeft
}
if (mThumbCenterX > mRight) {
mThumbCenterX = mRight
}
mProgress = (mThumbCenterX - mLeft) * mDelta / mTrackLength + mMin
invalidate()
}
dx = mThumbCenterX - event.getX()
break
case MotionEvent.ACTION_MOVE:
if (isThumbOnDragging) {
mThumbCenterX = event.getX() + dx
if (mThumbCenterX < mLeft) {
mThumbCenterX = mLeft
}
if (mThumbCenterX > mRight) {
mThumbCenterX = mRight
}
mProgress = (mThumbCenterX - mLeft) * mDelta / mTrackLength + mMin
invalidate()
if (mProgressListener != null) {
mProgressListener.onProgressChanged(this, getProgress(), true)
}
}
break
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false)
isThumbOnDragging = false
invalidate()
if (mProgressListener != null) {
mProgressListener.getProgressOnActionUp(this, getProgress())
}
break
}
return isThumbOnDragging || isTouchToSeek || super.onTouchEvent(event)
}
/**
* 计算新的透明度颜色
*
* @param color 旧颜色
* @param ratio 透明度系数
*/
public int getColorWithAlpha(int color, float ratio) {
int newColor = 0
int alpha = Math.round(Color.alpha(color) * ratio)
int r = Color.red(color)
int g = Color.green(color)
int b = Color.blue(color)
newColor = Color.argb(alpha, r, g, b)
return newColor
}
/**
* Detect effective touch of thumb
*/
private boolean isThumbTouched(MotionEvent event) {
if (!isEnabled()) {
return false
}
float mCircleR = isThumbOnDragging ? mThumbRadiusOnDragging : mThumbRadius
float x = mTrackLength / mDelta * (mProgress - mMin) + mLeft
float y = getMeasuredHeight() / 2f
return (event.getX() - x) * (event.getX() - x) + (event.getY() - y) * (event.getY() - y)
<= (mLeft + mCircleR) * (mLeft + mCircleR)
}
/**
* Detect effective touch of track
*/
private boolean isTrackTouched(MotionEvent event) {
return isEnabled() && event.getX() >= getPaddingLeft() && event.getX() <= getMeasuredWidth() - getPaddingRight()
&& event.getY() >= getPaddingTop() && event.getY() <= getMeasuredHeight() - getPaddingBottom()
}
public float getMin() {
return mMin
}
public float getMax() {
return mMax
}
public void setProgress(float progress) {
mProgress = progress
// if (mProgressListener != null) {
// mProgressListener.onProgressChanged(this, getProgress(), false)
// mProgressListener.getProgressOnFinally(this, getProgress(), false)
// }
postInvalidate()
}
public void setMax(float max) {
this.mMax = max
//设置max后初始化 delta
mDelta = mMax - mMin
postInvalidate()
}
public void setCustomArrayFloat(ArrayList<Float> customArrayFloat) {
mCustomArrayFloat = customArrayFloat
postInvalidate()
}
public void setCacheProgress(float cacheProgress) {
mCacheProgress = cacheProgress
postInvalidate()
}
public float getProgress() {
return mProgress
}
public void setOnProgressChangedListener(OnProgressChangedListener onProgressChangedListener) {
mProgressListener = onProgressChangedListener
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle()
bundle.putParcelable("save_instance", super.onSaveInstanceState())
bundle.putFloat("progress", mProgress)
return bundle
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state
mProgress = bundle.getFloat("progress")
super.onRestoreInstanceState(bundle.getParcelable("save_instance"))
setProgress(mProgress)
return
}
super.onRestoreInstanceState(state)
}
private String float2String(float value) {
return String.valueOf(formatFloat(value))
}
private float formatFloat(float value) {
BigDecimal bigDecimal = BigDecimal.valueOf(value)
return bigDecimal.setScale(1, BigDecimal.ROUND_HALF_UP).floatValue()
}
/**
* 进度监听 onChanged, onActionUp, onFinally
*/
public interface OnProgressChangedListener {
//进度条在拖动中,或执行动画中改变回调
void onProgressChanged(MarkSeekBar seekBar, float progress, boolean fromUser)
//手指抬起后改变进度条
void getProgressOnActionUp(MarkSeekBar seekBar, float progress)
}
}
seekBar的命名空间 放到attrs中
<declare-styleable name="MarkSeekBar">
<attr name="msb_min" format="float|reference"/>
<attr name="msb_max" format="float|reference"/>
<attr name="msb_progress" format="float|reference"/>
<attr name="msb_track_size" format="dimension|reference"/>
<attr name="msb_second_track_size" format="dimension|reference"/>
<attr name="msb_cache_track_size" format="dimension|reference"/>
<attr name="msb_thumb_radius" format="dimension|reference"/>
<attr name="msb_mark_radius" format="dimension|reference"/>
<attr name="msb_thumb_radius_on_dragging" format="dimension|reference"/>
<attr name="msb_track_color" format="color|reference"/>
<attr name="msb_second_track_color" format="color|reference"/>
<attr name="msb_cache_track_color" format="color|reference"/>
<attr name="msb_thumb_color" format="color|reference"/>
<attr name="msb_is_show_thumb" format="boolean"/>
<attr name="msb_show_section_mark" format="boolean"/>
<attr name="msb_section_mark_color" format="color|reference"/>
<attr name="msb_anim_duration" format="integer"/>
<attr name="msb_touch_to_seek" format="boolean"/>
<attr name="msb_thumb_ratio" format="float|reference"/>
<attr name="msb_thumb_bg_alpha" format="float|reference"/>
<attr name="msb_show_thumb_shadow" format="boolean"/>
</declare-styleable>