视频播放
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package com.mogo.och.taxi.passenger.bean;
|
||||
|
||||
public class TaxiPassengerVideoPlay {
|
||||
|
||||
public TaxiPassengerVideoPlay(String url, String imageUrl, String title) {
|
||||
this.url = url;
|
||||
this.imageUrl = imageUrl;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
private String url;
|
||||
private String imageUrl;
|
||||
private String title;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public void setImageUrl(String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
}
|
||||
@@ -121,16 +121,16 @@ public class TaxiPassengerBaseFragment extends MvpFragment<TaxiPassengerBaseFrag
|
||||
MogoMapUIController.getInstance().changeMapVisualAngle(VisualAngleMode.MODE_LONG_SIGHT, null);
|
||||
mMapswitchBtn.setImageResource(R.drawable.taxi_p_switch_map_long);
|
||||
}
|
||||
OverlayLeftViewUtils.INSTANCE.dismissOverlayView();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.iv_temp).setOnClickListener(view -> {
|
||||
//OverlayLeftViewUtils.INSTANCE.showOverlayView(getActivity());
|
||||
OverlayLeftViewUtils.INSTANCE.showOverlayView(getActivity());
|
||||
//showOrHideArrivedEndLayout(true, "北京北京北京", "1527481606997577728");
|
||||
//showOrHidePressengerCheckPager(true, "开始站点开", "开始站点开始站点开始", "2", "京A888888", "18811539480");
|
||||
//OCHFloatWindowManager.getInstance().ShowFloatWindow(getContext());
|
||||
OverlayViewUtils.showOverlayView(getActivity(),new TaxiPassengerMogoConsultView(getContext()));
|
||||
ToastUtils.showShort("测试点击");
|
||||
//OverlayViewUtils.showOverlayView(getActivity(),new TaxiPassengerMogoConsultView(getContext()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,17 @@ package com.mogo.och.taxi.passenger.ui.leftmenu
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
||||
class ItemViewTouchListener(
|
||||
private val wl: WindowManager.LayoutParams,
|
||||
private val windowManager: WindowManager?
|
||||
private val windowManager: WindowManager?,
|
||||
private val close: (view:View,windowManager: WindowManager?) -> Unit,
|
||||
private val open : (view:View,windowManager: WindowManager?) -> Unit
|
||||
) :
|
||||
View.OnTouchListener {
|
||||
private var x = 0
|
||||
// 判断并放跑点击事件
|
||||
private var dragTime = 0L
|
||||
private val DEVIATION = 10
|
||||
private val NEGATIVEDEVIATION = -10
|
||||
@@ -32,24 +36,30 @@ class ItemViewTouchListener(
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (wl.x > NEGATIVEDEVIATION && movedX > 0) {
|
||||
wl.x = 0
|
||||
open(view.rootView,windowManager)
|
||||
}else{
|
||||
//更新悬浮球控件位置
|
||||
windowManager?.updateViewLayout(view.rootView, wl)
|
||||
}
|
||||
if (wl.x < OverlayLeftViewUtils.DEVIATION_WIDTH +DEVIATION && movedX < 0) {
|
||||
wl.x = OverlayLeftViewUtils.DEVIATION_WIDTH
|
||||
close(view.rootView,windowManager)
|
||||
}else{
|
||||
//更新悬浮球控件位置
|
||||
windowManager?.updateViewLayout(view.rootView, wl)
|
||||
}
|
||||
|
||||
//更新悬浮球控件位置
|
||||
windowManager?.updateViewLayout(view.rootView, wl)
|
||||
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val startX = wl.x
|
||||
if (startX > OverlayLeftViewUtils.DEVIATION_WIDTH /2 && startX < 0) {
|
||||
wl.x = 0
|
||||
windowManager?.updateViewLayout(view.rootView, wl)
|
||||
//拖动距离大于一半 自动打开
|
||||
open(view.rootView,windowManager)
|
||||
} else if (startX < OverlayLeftViewUtils.DEVIATION_WIDTH /2 && startX >= OverlayLeftViewUtils.DEVIATION_WIDTH) {
|
||||
wl.x = OverlayLeftViewUtils.DEVIATION_WIDTH
|
||||
windowManager?.updateViewLayout(view.rootView, wl)
|
||||
// 拖动距离小于一半自动关闭
|
||||
close(view.rootView,windowManager)
|
||||
}
|
||||
if (System.currentTimeMillis() - dragTime > 500) {
|
||||
dragTime = 0
|
||||
|
||||
@@ -31,6 +31,11 @@ class ListAdapter(private val context: Context,val list: MutableList<LeftMenuMod
|
||||
}
|
||||
imageView.setOnClickListener {
|
||||
for (i in list.indices) {
|
||||
if(position==i){
|
||||
if(!list[i].isChecked){
|
||||
list[i].selectListener.onSelect(convertView)
|
||||
}
|
||||
}
|
||||
list[i].isChecked = position == i
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
@@ -38,4 +43,13 @@ class ListAdapter(private val context: Context,val list: MutableList<LeftMenuMod
|
||||
return imageView
|
||||
}
|
||||
|
||||
interface OnTabSelectListener {
|
||||
/**
|
||||
* Called when a view has been clicked.
|
||||
*
|
||||
* @param v The view that was clicked.
|
||||
*/
|
||||
fun onSelect(v: View?)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,12 +9,19 @@ import android.graphics.Region
|
||||
import android.view.*
|
||||
import android.widget.ListView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
|
||||
import com.mogo.eagle.core.utilcode.util.OverlayViewUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils
|
||||
import com.mogo.och.taxi.passenger.R
|
||||
import com.mogo.och.taxi.passenger.ui.leftmenu.model.LeftMenuModel
|
||||
import com.mogo.och.taxi.passenger.ui.video.TaxiPassengerMogoConsultView
|
||||
import com.mogo.och.taxi.passenger.ui.video.TaxiPassengerMogoMoviesView
|
||||
import com.mogo.och.taxi.passenger.utils.windowdispatch.OnComputeInternalInsetsListener
|
||||
import com.mogo.och.taxi.passenger.utils.windowdispatch.ReflectionUtils
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* 遮罩层工具类
|
||||
@@ -42,7 +49,12 @@ object OverlayLeftViewUtils {
|
||||
/**
|
||||
* 记录上一次的View
|
||||
*/
|
||||
private var lastOverlayView: View? = null
|
||||
private var overlayView: View?=null
|
||||
|
||||
private var subscribe: Disposable?=null
|
||||
|
||||
private var taxiPassengerMogoConsultView: WeakReference<TaxiPassengerMogoConsultView>? = null
|
||||
private var taxiPassengerMogoMoviesView: WeakReference<TaxiPassengerMogoMoviesView>? = null
|
||||
|
||||
/**
|
||||
* 添加覆盖View在Activity上面
|
||||
@@ -55,66 +67,140 @@ object OverlayLeftViewUtils {
|
||||
if (windowManager == null) {
|
||||
windowManager = context.windowManager
|
||||
}
|
||||
val overlayView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.taxi_p_window_float_interphone, null) as ConstraintLayout
|
||||
// 设置View显示模式,沉浸式的侵入到状态栏,导航栏
|
||||
overlayView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
layoutParams(ani)
|
||||
|
||||
// 如果正在展示中,并且lastOverlayView不为null,先做移除操作,保证覆盖在最上面的View只有一个,防止叠加导致无法移除
|
||||
if (lastOverlayView != null) {
|
||||
dismissOverlayView(lastOverlayView)
|
||||
}
|
||||
val vDragField = overlayView.findViewById<View>(R.id.v_drag_field)
|
||||
vDragField.setOnTouchListener(ItemViewTouchListener(params!!, windowManager))
|
||||
vDragField.setOnClickListener {
|
||||
val start: Int = params!!.x
|
||||
if (start > DEVIATION_WIDTH /2 && start < 10) {
|
||||
params?.x = DEVIATION_WIDTH
|
||||
windowManager?.updateViewLayout(overlayView, params)
|
||||
} else if (start < DEVIATION_WIDTH /2 && start >= DEVIATION_WIDTH) {
|
||||
params?.x = 0
|
||||
windowManager?.updateViewLayout(overlayView, params)
|
||||
overlayView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.taxi_p_window_float_interphone, null) as ConstraintLayout
|
||||
overlayView?.let { view ->
|
||||
// 设置View显示模式,沉浸式的侵入到状态栏,导航栏
|
||||
view.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
layoutParams(ani)
|
||||
|
||||
// 如果正在展示中,并且lastOverlayView不为null,先做移除操作,保证覆盖在最上面的View只有一个,防止叠加导致无法移除
|
||||
dismissOverlayView()
|
||||
|
||||
val vDragField = view.findViewById<View>(R.id.v_drag_field)
|
||||
vDragField.setOnTouchListener(ItemViewTouchListener(params!!, windowManager, ::close,
|
||||
::open))
|
||||
vDragField.setOnClickListener {
|
||||
val start: Int = params!!.x
|
||||
if (start > DEVIATION_WIDTH /2 && start < 10) {
|
||||
close(view, windowManager)
|
||||
} else if (start < DEVIATION_WIDTH /2 && start >= DEVIATION_WIDTH) {
|
||||
open(view, windowManager)
|
||||
}
|
||||
}
|
||||
|
||||
val lvSelectItem = view.findViewById<ListView>(R.id.lv_select_item)
|
||||
val integers = mutableListOf<LeftMenuModel>()
|
||||
|
||||
val liveSelected = object :ListAdapter.OnTabSelectListener{
|
||||
override fun onSelect(v: View?) {
|
||||
close(view, windowManager)
|
||||
if(taxiPassengerMogoConsultView?.get() != null){
|
||||
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoConsultView?.get())
|
||||
}
|
||||
if(taxiPassengerMogoMoviesView?.get() != null){
|
||||
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoMoviesView?.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val consultSelect = object :ListAdapter.OnTabSelectListener{
|
||||
override fun onSelect(v: View?) {
|
||||
close(view, windowManager)
|
||||
if(taxiPassengerMogoMoviesView?.get() != null){
|
||||
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoMoviesView?.get())
|
||||
}
|
||||
if(taxiPassengerMogoConsultView?.get() != null){
|
||||
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoConsultView?.get())
|
||||
}else{
|
||||
ToastUtils.showLong("已经回收")
|
||||
taxiPassengerMogoConsultView =
|
||||
WeakReference<TaxiPassengerMogoConsultView>(TaxiPassengerMogoConsultView(context))
|
||||
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoConsultView?.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val entertainmentSelect = object :ListAdapter.OnTabSelectListener{
|
||||
override fun onSelect(v: View?) {
|
||||
close(view, windowManager)
|
||||
if(taxiPassengerMogoConsultView?.get() != null){
|
||||
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoConsultView?.get())
|
||||
}
|
||||
if(taxiPassengerMogoMoviesView?.get() != null){
|
||||
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoMoviesView?.get())
|
||||
}else{
|
||||
ToastUtils.showLong("已经回收")
|
||||
taxiPassengerMogoMoviesView =
|
||||
WeakReference<TaxiPassengerMogoMoviesView>(TaxiPassengerMogoMoviesView(context))
|
||||
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoMoviesView?.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_live_select,R.drawable.taxi_p_mogo_live_selected,true,liveSelected))
|
||||
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_consult_select,R.drawable.taxi_p_mogo_consult_selected,false,consultSelect))
|
||||
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_entertainment_select,R.drawable.taxi_p_mogo_entertainment_selected,false,entertainmentSelect))
|
||||
lvSelectItem.adapter = ListAdapter(context, integers)
|
||||
|
||||
view.viewTreeObserver
|
||||
.addOnGlobalLayoutListener(object :ViewTreeObserver.OnGlobalLayoutListener{
|
||||
override fun onGlobalLayout() {
|
||||
mTouchRegion.setEmpty()
|
||||
mTouchRegion.op(getViewBounds(vDragField), Region.Op.UNION)
|
||||
mTouchRegion.op(getViewBounds(lvSelectItem), Region.Op.UNION)
|
||||
mInvocationHandler?.touchRegion = mTouchRegion
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
}
|
||||
})
|
||||
try {
|
||||
mInvocationHandler =
|
||||
OnComputeInternalInsetsListener()
|
||||
ReflectionUtils.removeOnComputeInternalInsetsListener(view.viewTreeObserver)
|
||||
ReflectionUtils.addOnComputeInternalInsetsListener(
|
||||
view.viewTreeObserver,
|
||||
mInvocationHandler?.getListener()
|
||||
)
|
||||
mInvocationHandler?.touchRegion = mTouchRegion
|
||||
windowManager!!.addView(overlayView, params)
|
||||
isShowing = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lvSelectItem = overlayView.findViewById<ListView>(R.id.lv_select_item)
|
||||
val integers = mutableListOf<LeftMenuModel>()
|
||||
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_live_selected,R.drawable.taxi_p_mogo_live_selected,true))
|
||||
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_overview_selected,R.drawable.taxi_p_mogo_overview_selected,false))
|
||||
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_consult_select,R.drawable.taxi_p_mogo_consult_selected,false))
|
||||
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_entertainment_select,R.drawable.taxi_p_mogo_entertainment_selected,false))
|
||||
lvSelectItem.adapter = ListAdapter(context, integers)
|
||||
|
||||
overlayView.viewTreeObserver
|
||||
.addOnGlobalLayoutListener(object :ViewTreeObserver.OnGlobalLayoutListener{
|
||||
override fun onGlobalLayout() {
|
||||
mTouchRegion.setEmpty()
|
||||
mTouchRegion.op(getViewBounds(vDragField), Region.Op.UNION)
|
||||
mTouchRegion.op(getViewBounds(lvSelectItem), Region.Op.UNION)
|
||||
mInvocationHandler?.touchRegion = mTouchRegion
|
||||
overlayView.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
}
|
||||
})
|
||||
try {
|
||||
lastOverlayView = overlayView
|
||||
mInvocationHandler =
|
||||
OnComputeInternalInsetsListener()
|
||||
ReflectionUtils.removeOnComputeInternalInsetsListener(overlayView.viewTreeObserver)
|
||||
ReflectionUtils.addOnComputeInternalInsetsListener(
|
||||
overlayView.viewTreeObserver,
|
||||
mInvocationHandler?.getListener()
|
||||
)
|
||||
mInvocationHandler?.touchRegion = mTouchRegion
|
||||
windowManager!!.addView(overlayView, params)
|
||||
isShowing = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
/**
|
||||
* 打开状态栏
|
||||
*/
|
||||
private fun open(overlayView: View,windowManager: WindowManager?) {
|
||||
params?.x = 0
|
||||
windowManager?.updateViewLayout(overlayView, params)
|
||||
subscribe?.let {
|
||||
if (!it.isDisposed) {
|
||||
it.dispose()
|
||||
}
|
||||
}
|
||||
subscribe = Observable.timer(3000, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
if(params?.x==0){
|
||||
close(overlayView,windowManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭状态栏
|
||||
*/
|
||||
fun close(overlayView: View,windowManager: WindowManager?) {
|
||||
params?.x = DEVIATION_WIDTH
|
||||
windowManager?.updateViewLayout(overlayView, params)
|
||||
}
|
||||
|
||||
private fun layoutParams(ani: Int) {
|
||||
@@ -144,17 +230,19 @@ object OverlayLeftViewUtils {
|
||||
/**
|
||||
* 移除覆盖View在Activity上面
|
||||
*/
|
||||
fun dismissOverlayView(overlayView: View?) {
|
||||
fun dismissOverlayView() {
|
||||
if (!isShowing) {
|
||||
return
|
||||
}
|
||||
subscribe?.let {
|
||||
if (!it.isDisposed) {
|
||||
it.dispose()
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (windowManager != null && overlayView != null) {
|
||||
windowManager!!.removeView(overlayView)
|
||||
}
|
||||
if (lastOverlayView != null && lastOverlayView === overlayView) {
|
||||
lastOverlayView = null
|
||||
}
|
||||
isShowing = false
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -162,10 +250,6 @@ object OverlayLeftViewUtils {
|
||||
}
|
||||
|
||||
private fun getViewBounds(view: View): Rect {
|
||||
CallerLogger.e(
|
||||
SceneConstant.M_TAXI_P + "ItemViewTouchListener",
|
||||
"点击的位置${view.left}----${view.top}---${view.right}-----${view.bottom}"
|
||||
)
|
||||
return Rect(view.left, view.top, view.right, view.bottom)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.mogo.och.taxi.passenger.ui.leftmenu.model
|
||||
|
||||
import com.mogo.och.taxi.passenger.ui.leftmenu.ListAdapter
|
||||
|
||||
data class LeftMenuModel(
|
||||
val select: Int,
|
||||
val selected: Int,
|
||||
var isChecked: Boolean
|
||||
var isChecked: Boolean,
|
||||
val selectListener: ListAdapter.OnTabSelectListener
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
@@ -16,7 +17,7 @@ public class RecyclerItemVideoHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
protected Context context;
|
||||
|
||||
ConsultVideoPlayer gsyVideoPlayer;
|
||||
public ConsultVideoPlayer gsyVideoPlayer;
|
||||
|
||||
ImageView imageView;
|
||||
|
||||
@@ -30,18 +31,4 @@ public class RecyclerItemVideoHolder extends RecyclerView.ViewHolder {
|
||||
gsyVideoOptionBuilder = new GSYVideoOptionBuilder();
|
||||
}
|
||||
|
||||
public void onBind(final int position, String videoModel) {
|
||||
|
||||
String url;
|
||||
if (position % 2 == 0) {
|
||||
url = "https://pointshow.oss-cn-hangzhou.aliyuncs.com/McTk51586843620689.mp4";
|
||||
} else {
|
||||
url = "http://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4";
|
||||
}
|
||||
gsyVideoOptionBuilder.setUrl(url).setCacheWithPlay(false).setPlayTag("NoticeTrafficDialog")
|
||||
.build(gsyVideoPlayer);
|
||||
gsyVideoPlayer.getStartButton().performClick();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -8,21 +9,32 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.mogo.eagle.core.widget.glide.SkinAbleBitmapTarget;
|
||||
import com.mogo.och.taxi.passenger.R;
|
||||
import com.mogo.och.taxi.passenger.bean.TaxiPassengerVideoPlay;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RecyclerVideoAdapter extends RecyclerView.Adapter<RecyclerItemVideoHolder> {
|
||||
private final static String TAG = "RecyclerBaseAdapter";
|
||||
|
||||
private List<String> itemDataList = null;
|
||||
private List<TaxiPassengerVideoPlay> itemDataList = null;
|
||||
private Context context = null;
|
||||
|
||||
public RecyclerVideoAdapter(Context context, List<String> itemDataList) {
|
||||
public RecyclerVideoAdapter(Context context, List<TaxiPassengerVideoPlay> itemDataList) {
|
||||
this.itemDataList = itemDataList;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public TaxiPassengerVideoPlay getItemByPosition(int position){
|
||||
if(itemDataList!=null){
|
||||
return itemDataList.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerItemVideoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
@@ -32,7 +44,14 @@ public class RecyclerVideoAdapter extends RecyclerView.Adapter<RecyclerItemVideo
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final RecyclerItemVideoHolder holder, int position) {
|
||||
holder.onBind(position, itemDataList.get(position));
|
||||
final TaxiPassengerVideoPlay taxiPassengerVideoPlay = itemDataList.get(position);
|
||||
|
||||
holder.gsyVideoOptionBuilder.setUrl(taxiPassengerVideoPlay.getUrl()).setCacheWithPlay(true).setPlayTag(taxiPassengerVideoPlay.getImageUrl()+position)
|
||||
.build(holder.gsyVideoPlayer);
|
||||
Glide.with(context)
|
||||
.load(taxiPassengerVideoPlay.getImageUrl())
|
||||
.apply(new RequestOptions().centerCrop())
|
||||
.into(holder.gsyVideoPlayer.coverImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,9 +64,5 @@ public class RecyclerVideoAdapter extends RecyclerView.Adapter<RecyclerItemVideo
|
||||
public int getItemViewType(int position) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void setListData(List<String> data) {
|
||||
itemDataList = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
|
||||
import com.mogo.och.taxi.passenger.R
|
||||
import com.mogo.och.taxi.passenger.bean.TaxiPassengerVideoPlay
|
||||
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CarouselLayoutManager
|
||||
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CarouselZoomPostLayoutListener
|
||||
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CenterScrollListener
|
||||
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer
|
||||
import com.mogo.och.taxi.passenger.utils.blur.GlideBlurTransform
|
||||
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
|
||||
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
@@ -29,31 +45,156 @@ class TaxiPassengerMogoConsultView :RelativeLayout {
|
||||
|
||||
|
||||
private lateinit var rvVideoPlaylist: RecyclerView
|
||||
|
||||
private var subscribe: Disposable?=null
|
||||
private lateinit var clContain: ConstraintLayout
|
||||
|
||||
private fun initView(context: Context) {
|
||||
d(SceneConstant.M_TAXI_P + "pageStopCenterScrollListener", "initView:$visibility")
|
||||
d(SceneConstant.M_TAXI_P + TAG, "initView")
|
||||
LayoutInflater.from(context).inflate(R.layout.taxi_p_arrived_mogo_consult, this, true)
|
||||
rvVideoPlaylist = findViewById(R.id.rv_video_playlist)
|
||||
clContain = findViewById(R.id.cl_contain)
|
||||
|
||||
val arrayListOf = ArrayList<String>()
|
||||
arrayListOf.add("")
|
||||
arrayListOf.add("")
|
||||
val arrayListOf = ArrayList<TaxiPassengerVideoPlay>()
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.png","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/12111.jpg","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.png","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/12111.jpg","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.png","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/12111.jpg","title"))
|
||||
//TaxiPassengerMogoMoviesView
|
||||
val recyclerVideoAdapter = RecyclerVideoAdapter(context, arrayListOf)
|
||||
val linearLayoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
|
||||
rvVideoPlaylist.layoutManager = linearLayoutManager
|
||||
val carouselLayoutManager = CarouselLayoutManager(CarouselLayoutManager.HORIZONTAL, true)
|
||||
carouselLayoutManager.setPostLayoutListener(CarouselZoomPostLayoutListener ())
|
||||
carouselLayoutManager.maxVisibleItems = 1
|
||||
rvVideoPlaylist.addOnScrollListener(object: CenterScrollListener() {
|
||||
var currentPausePlayer = -1
|
||||
var prePlayer:ConsultVideoPlayer?=null
|
||||
override fun pageSelect(recyclerView: RecyclerView?, newState: Int) {
|
||||
//播放视频
|
||||
val centerItemPosition: Int = carouselLayoutManager.getCenterItemPosition()
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
if(player is ConsultVideoPlayer){
|
||||
if(currentPausePlayer==-1||currentPausePlayer!=centerItemPosition) {
|
||||
if(prePlayer!=null){
|
||||
prePlayer?.onVideoReset()
|
||||
prePlayer = null
|
||||
}
|
||||
if(player.currentState==GSYVideoView.CURRENT_STATE_PAUSE){
|
||||
player.onVideoReset()
|
||||
player.startPlayLogic()
|
||||
}else{
|
||||
player.startPlayLogic()
|
||||
}
|
||||
val taxiPassengerVideoPlay = arrayListOf[centerItemPosition]
|
||||
setBackageAndPlayNext(taxiPassengerVideoPlay, player, centerItemPosition)
|
||||
}else{
|
||||
player.onVideoResume(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageStop() {
|
||||
val centerItemPosition: Int = carouselLayoutManager.getCenterItemPosition()
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
if(player is ConsultVideoPlayer){
|
||||
player.onVideoPause()
|
||||
prePlayer = player;
|
||||
currentPausePlayer = centerItemPosition
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
rvVideoPlaylist.layoutManager = carouselLayoutManager
|
||||
rvVideoPlaylist.setHasFixedSize(true)
|
||||
rvVideoPlaylist.adapter = recyclerVideoAdapter
|
||||
}
|
||||
|
||||
private fun setBackageAndPlayNext(
|
||||
taxiPassengerVideoPlay: TaxiPassengerVideoPlay,
|
||||
player: ConsultVideoPlayer,
|
||||
centerItemPosition: Int,
|
||||
) {
|
||||
// 设置背景图片
|
||||
Glide.with(context).asBitmap()
|
||||
.load(taxiPassengerVideoPlay.imageUrl)
|
||||
.apply(
|
||||
RequestOptions().transform(
|
||||
GlideBlurTransform(
|
||||
context,
|
||||
taxiPassengerVideoPlay.imageUrl,
|
||||
5
|
||||
)
|
||||
)
|
||||
)
|
||||
.into(object : SimpleTarget<Bitmap?>() {
|
||||
override fun onResourceReady(
|
||||
resource: Bitmap,
|
||||
transition: Transition<in Bitmap?>?
|
||||
) {
|
||||
clContain.background = BitmapDrawable(context.resources, resource)
|
||||
}
|
||||
})
|
||||
|
||||
player.setVideoAllCallBack(object : GSYSampleCallBack() {
|
||||
override fun onAutoComplete(url: String?, vararg objects: Any?) {
|
||||
player.onVideoReset()
|
||||
val itemCount = rvVideoPlaylist.adapter?.itemCount
|
||||
itemCount?.let {
|
||||
if (centerItemPosition == itemCount - 1) {
|
||||
rvVideoPlaylist.smoothScrollToPosition(0)
|
||||
} else {
|
||||
rvVideoPlaylist.smoothScrollToPosition(centerItemPosition + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasWindowFocus)
|
||||
val carouselLayoutManager = rvVideoPlaylist.layoutManager as CarouselLayoutManager
|
||||
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
player?.let {
|
||||
if (player is ConsultVideoPlayer) {
|
||||
if(hasWindowFocus){// 获取焦点两种情况
|
||||
// 恢复播放和开始播放
|
||||
when (player.currentState) {
|
||||
GSYVideoView.CURRENT_STATE_PAUSE -> {
|
||||
player.onVideoResume(false)
|
||||
}
|
||||
else -> {
|
||||
val recyclerVideoAdapter =
|
||||
rvVideoPlaylist.adapter as RecyclerVideoAdapter
|
||||
setBackageAndPlayNext(recyclerVideoAdapter.getItemByPosition(centerItemPosition), player, centerItemPosition)
|
||||
player.startPlayLogic()
|
||||
}
|
||||
}
|
||||
}else {
|
||||
// 离开应用 暂停视频
|
||||
// 关闭 onDetachedFromWindow 会reset
|
||||
player.onVideoPause()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
subscribe?.let {
|
||||
if (!it.isDisposed) {
|
||||
it.dispose()
|
||||
val carouselLayoutManager = rvVideoPlaylist.layoutManager as CarouselLayoutManager
|
||||
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
d(SceneConstant.M_TAXI_P + "pageStopCenterScrollListener", "onDetachedFromWindow:$visibility---$player---${centerItemPosition}")
|
||||
player?.let {
|
||||
if(player is ConsultVideoPlayer){
|
||||
player.onVideoReset()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
|
||||
import com.mogo.och.taxi.passenger.R
|
||||
import com.mogo.och.taxi.passenger.bean.TaxiPassengerVideoPlay
|
||||
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CarouselLayoutManager
|
||||
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CarouselZoomPostLayoutListener
|
||||
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CenterScrollListener
|
||||
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer
|
||||
import com.mogo.och.taxi.passenger.utils.blur.GlideBlurTransform
|
||||
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
|
||||
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 蘑菇咨询
|
||||
* Created on 2022/5/16
|
||||
*/
|
||||
class TaxiPassengerMogoMoviesView :RelativeLayout {
|
||||
|
||||
constructor(context: Context?) : super(context)
|
||||
|
||||
constructor(context: Context?, attributeSet: AttributeSet) : super(context, attributeSet)
|
||||
|
||||
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
|
||||
|
||||
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
|
||||
|
||||
|
||||
private lateinit var rvVideoPlaylist: RecyclerView
|
||||
private lateinit var clContain: ConstraintLayout
|
||||
|
||||
private fun initView(context: Context) {
|
||||
d(SceneConstant.M_TAXI_P + "pageStopCenterScrollListener", "initView:$visibility")
|
||||
d(SceneConstant.M_TAXI_P + TAG, "initView")
|
||||
LayoutInflater.from(context).inflate(R.layout.taxi_p_arrived_mogo_consult, this, true)
|
||||
rvVideoPlaylist = findViewById(R.id.rv_video_playlist)
|
||||
clContain = findViewById(R.id.cl_contain)
|
||||
|
||||
val arrayListOf = ArrayList<TaxiPassengerVideoPlay>()
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.png","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/12111.jpg","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.png","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/12111.jpg","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/McTk51586843620689.png","title"))
|
||||
arrayListOf.add(TaxiPassengerVideoPlay("https://gohome-1253308323.cos.ap-beijing.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4","https://gohome-1253308323.cos.ap-beijing.myqcloud.com/12111.jpg","title"))
|
||||
//TaxiPassengerMogoMoviesView
|
||||
val recyclerVideoAdapter = RecyclerVideoAdapter(context, arrayListOf)
|
||||
val carouselLayoutManager = CarouselLayoutManager(CarouselLayoutManager.HORIZONTAL, true)
|
||||
carouselLayoutManager.setPostLayoutListener(CarouselZoomPostLayoutListener ())
|
||||
carouselLayoutManager.maxVisibleItems = 1
|
||||
rvVideoPlaylist.addOnScrollListener(object: CenterScrollListener() {
|
||||
var currentPausePlayer = -1
|
||||
var prePlayer:ConsultVideoPlayer?=null
|
||||
override fun pageSelect(recyclerView: RecyclerView?, newState: Int) {
|
||||
//播放视频
|
||||
val centerItemPosition: Int = carouselLayoutManager.getCenterItemPosition()
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
if(player is ConsultVideoPlayer){
|
||||
if(currentPausePlayer==-1||currentPausePlayer!=centerItemPosition) {
|
||||
if(prePlayer!=null){
|
||||
prePlayer?.onVideoReset()
|
||||
prePlayer = null
|
||||
}
|
||||
if(player.currentState==GSYVideoView.CURRENT_STATE_PAUSE){
|
||||
player.onVideoReset()
|
||||
player.startPlayLogic()
|
||||
}else{
|
||||
player.startPlayLogic()
|
||||
}
|
||||
val taxiPassengerVideoPlay = arrayListOf[centerItemPosition]
|
||||
setBackageAndPlayNext(taxiPassengerVideoPlay, player, centerItemPosition)
|
||||
}else{
|
||||
player.onVideoResume(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageStop() {
|
||||
val centerItemPosition: Int = carouselLayoutManager.getCenterItemPosition()
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
if(player is ConsultVideoPlayer){
|
||||
player.onVideoPause()
|
||||
prePlayer = player;
|
||||
currentPausePlayer = centerItemPosition
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
rvVideoPlaylist.layoutManager = carouselLayoutManager
|
||||
rvVideoPlaylist.setHasFixedSize(true)
|
||||
rvVideoPlaylist.adapter = recyclerVideoAdapter
|
||||
}
|
||||
|
||||
private fun setBackageAndPlayNext(
|
||||
taxiPassengerVideoPlay: TaxiPassengerVideoPlay,
|
||||
player: ConsultVideoPlayer,
|
||||
centerItemPosition: Int,
|
||||
) {
|
||||
// 设置背景图片
|
||||
Glide.with(context).asBitmap()
|
||||
.load(taxiPassengerVideoPlay.imageUrl)
|
||||
.apply(
|
||||
RequestOptions().transform(
|
||||
GlideBlurTransform(
|
||||
context,
|
||||
taxiPassengerVideoPlay.imageUrl,
|
||||
5
|
||||
)
|
||||
)
|
||||
)
|
||||
.into(object : SimpleTarget<Bitmap?>() {
|
||||
override fun onResourceReady(
|
||||
resource: Bitmap,
|
||||
transition: Transition<in Bitmap?>?
|
||||
) {
|
||||
clContain.background = BitmapDrawable(context.resources, resource)
|
||||
}
|
||||
})
|
||||
|
||||
player.setVideoAllCallBack(object : GSYSampleCallBack() {
|
||||
override fun onAutoComplete(url: String?, vararg objects: Any?) {
|
||||
player.onVideoReset()
|
||||
val itemCount = rvVideoPlaylist.adapter?.itemCount
|
||||
itemCount?.let {
|
||||
if (centerItemPosition == itemCount - 1) {
|
||||
rvVideoPlaylist.smoothScrollToPosition(0)
|
||||
} else {
|
||||
rvVideoPlaylist.smoothScrollToPosition(centerItemPosition + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasWindowFocus)
|
||||
val carouselLayoutManager = rvVideoPlaylist.layoutManager as CarouselLayoutManager
|
||||
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
player?.let {
|
||||
if (player is ConsultVideoPlayer) {
|
||||
if(hasWindowFocus){// 获取焦点两种情况
|
||||
// 恢复播放和开始播放
|
||||
when (player.currentState) {
|
||||
GSYVideoView.CURRENT_STATE_PAUSE -> {
|
||||
player.onVideoResume(false)
|
||||
}
|
||||
else -> {
|
||||
val recyclerVideoAdapter =
|
||||
rvVideoPlaylist.adapter as RecyclerVideoAdapter
|
||||
setBackageAndPlayNext(recyclerVideoAdapter.getItemByPosition(centerItemPosition), player, centerItemPosition)
|
||||
player.startPlayLogic()
|
||||
}
|
||||
}
|
||||
}else {
|
||||
// 离开应用 暂停视频
|
||||
// 关闭 onDetachedFromWindow 会reset
|
||||
player.onVideoPause()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
val carouselLayoutManager = rvVideoPlaylist.layoutManager as CarouselLayoutManager
|
||||
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
|
||||
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
d(SceneConstant.M_TAXI_P + "pageStopCenterScrollListener", "onDetachedFromWindow:$visibility---$player---${centerItemPosition}")
|
||||
player?.let {
|
||||
if(player is ConsultVideoPlayer){
|
||||
player.onVideoReset()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "TaxiPassengerMogoConsultView"
|
||||
}
|
||||
|
||||
init {
|
||||
try {
|
||||
initView(context)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class CarouselChildSelectionListener {
|
||||
|
||||
@NonNull
|
||||
private final RecyclerView mRecyclerView;
|
||||
@NonNull
|
||||
private final CarouselLayoutManager mCarouselLayoutManager;
|
||||
|
||||
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
final RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
|
||||
final int position = holder.getAdapterPosition();
|
||||
|
||||
if (position == mCarouselLayoutManager.getCenterItemPosition()) {
|
||||
onCenterItemClicked(mRecyclerView, mCarouselLayoutManager, v);
|
||||
} else {
|
||||
onBackItemClicked(mRecyclerView, mCarouselLayoutManager, v);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected CarouselChildSelectionListener(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager) {
|
||||
mRecyclerView = recyclerView;
|
||||
mCarouselLayoutManager = carouselLayoutManager;
|
||||
|
||||
mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onChildViewAttachedToWindow(@NonNull final View view) {
|
||||
view.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildViewDetachedFromWindow(@NonNull final View view) {
|
||||
view.setOnClickListener(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager, @NonNull final View v);
|
||||
|
||||
protected abstract void onBackItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager, @NonNull final View v);
|
||||
}
|
||||
@@ -0,0 +1,944 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.OrientationHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An implementation of {@link RecyclerView.LayoutManager} that layout items like carousel.
|
||||
* Generally there is one center item and bellow this item there are maximum {@link CarouselLayoutManager#getMaxVisibleItems()} items on each side of the center
|
||||
* item. By default {@link CarouselLayoutManager#getMaxVisibleItems()} is {@link CarouselLayoutManager#MAX_VISIBLE_ITEMS}.<br />
|
||||
* <br />
|
||||
* This LayoutManager supports only fixedSized adapter items.<br />
|
||||
* <br />
|
||||
* This LayoutManager supports {@link CarouselLayoutManager#HORIZONTAL} and {@link CarouselLayoutManager#VERTICAL} orientations. <br />
|
||||
* <br />
|
||||
* This LayoutManager supports circle layout. By default it if disabled. We don't recommend to use circle layout with adapter items count less then 3. <br />
|
||||
* <br />
|
||||
* Please be sure that layout_width of adapter item is a constant value and not {@link ViewGroup.LayoutParams#MATCH_PARENT}
|
||||
* for {@link #HORIZONTAL} orientation.
|
||||
* So like layout_height is not {@link ViewGroup.LayoutParams#MATCH_PARENT} for {@link CarouselLayoutManager#VERTICAL}<br />
|
||||
* <br />
|
||||
*/
|
||||
public class CarouselLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider {
|
||||
|
||||
public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
|
||||
public static final int VERTICAL = OrientationHelper.VERTICAL;
|
||||
/**
|
||||
* 固定值一直不变
|
||||
*/
|
||||
public static final int INVALID_POSITION = -1;
|
||||
public static final int MAX_VISIBLE_ITEMS = 3;
|
||||
|
||||
private static final boolean CIRCLE_LAYOUT = false;
|
||||
|
||||
private boolean mDecoratedChildSizeInvalid;
|
||||
private Integer mDecoratedChildWidth;
|
||||
private Integer mDecoratedChildHeight;
|
||||
|
||||
private final int mOrientation;
|
||||
private boolean mCircleLayout;
|
||||
|
||||
private int mPendingScrollPosition;
|
||||
|
||||
private final LayoutHelper mLayoutHelper = new LayoutHelper(MAX_VISIBLE_ITEMS);
|
||||
|
||||
private PostLayoutListener mViewPostLayout;
|
||||
|
||||
private final List<OnCenterItemSelectionListener> mOnCenterItemSelectionListeners = new ArrayList<>();
|
||||
private int mCenterItemPosition = INVALID_POSITION;
|
||||
private int mItemsCount;
|
||||
|
||||
@Nullable
|
||||
private CarouselSavedState mPendingCarouselSavedState;
|
||||
|
||||
/**
|
||||
* @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public CarouselLayoutManager(final int orientation) {
|
||||
this(orientation, CIRCLE_LAYOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* If circleLayout is true then all items will be in cycle. Scroll will be infinite on both sides.
|
||||
*
|
||||
* @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
|
||||
* @param circleLayout true for enabling circleLayout
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public CarouselLayoutManager(final int orientation, final boolean circleLayout) {
|
||||
if (HORIZONTAL != orientation && VERTICAL != orientation) {
|
||||
throw new IllegalArgumentException("orientation should be HORIZONTAL or VERTICAL");
|
||||
}
|
||||
mOrientation = orientation;
|
||||
mCircleLayout = circleLayout;
|
||||
mPendingScrollPosition = INVALID_POSITION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change circle layout type
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setCircleLayout(final boolean circleLayout) {
|
||||
if (mCircleLayout != circleLayout) {
|
||||
mCircleLayout = circleLayout;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup {@link PostLayoutListener} for this LayoutManager.
|
||||
* Its methods will be called for each visible view item after general LayoutManager layout finishes. <br />
|
||||
* <br />
|
||||
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
|
||||
*
|
||||
* @param postLayoutListener listener for item layout changes. Can be null.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setPostLayoutListener(@Nullable final PostLayoutListener postLayoutListener) {
|
||||
mViewPostLayout = postLayoutListener;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup maximum visible (layout) items on each side of the center item.
|
||||
* Basically during scrolling there can be more visible items (+1 item on each side), but in idle state this is the only reached maximum.
|
||||
*
|
||||
* @param maxVisibleItems should be great then 0, if bot an {@link IllegalAccessException} will be thrown
|
||||
*/
|
||||
@CallSuper
|
||||
@SuppressWarnings("unused")
|
||||
public void setMaxVisibleItems(final int maxVisibleItems) {
|
||||
if (0 > maxVisibleItems) {
|
||||
throw new IllegalArgumentException("maxVisibleItems can't be less then 0");
|
||||
}
|
||||
mLayoutHelper.mMaxVisibleItems = maxVisibleItems;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current setup for maximum visible items.
|
||||
* @see #setMaxVisibleItems(int)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public int getMaxVisibleItems() {
|
||||
return mLayoutHelper.mMaxVisibleItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current layout orientation
|
||||
* @see #VERTICAL
|
||||
* @see #HORIZONTAL
|
||||
*/
|
||||
public int getOrientation() {
|
||||
return mOrientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return 0 != getChildCount() && HORIZONTAL == mOrientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return 0 != getChildCount() && VERTICAL == mOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current layout center item
|
||||
*/
|
||||
public int getCenterItemPosition() {
|
||||
return mCenterItemPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onCenterItemSelectionListener listener that will trigger when ItemSelectionChanges. can't be null
|
||||
*/
|
||||
public void addOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) {
|
||||
mOnCenterItemSelectionListeners.add(onCenterItemSelectionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onCenterItemSelectionListener listener that was previously added by {@link #addOnItemSelectionListener(OnCenterItemSelectionListener)}
|
||||
*/
|
||||
public void removeOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) {
|
||||
mOnCenterItemSelectionListeners.remove(onCenterItemSelectionListener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RefusedBequest")
|
||||
@Override
|
||||
public void scrollToPosition(final int position) {
|
||||
if (0 > position) {
|
||||
throw new IllegalArgumentException("position can't be less then 0. position is : " + position);
|
||||
}
|
||||
mPendingScrollPosition = position;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@SuppressWarnings("RefusedBequest")
|
||||
@Override
|
||||
public void smoothScrollToPosition(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.State state, final int position) {
|
||||
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
|
||||
@Override
|
||||
public int calculateDyToMakeVisible(final View view, final int snapPreference) {
|
||||
if (!canScrollVertically()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getOffsetForCurrentView(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateDxToMakeVisible(final View view, final int snapPreference) {
|
||||
if (!canScrollHorizontally()) {
|
||||
return 0;
|
||||
}
|
||||
return getOffsetForCurrentView(view);
|
||||
}
|
||||
};
|
||||
linearSmoothScroller.setTargetPosition(position);
|
||||
startSmoothScroll(linearSmoothScroller);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PointF computeScrollVectorForPosition(final int targetPosition) {
|
||||
if (0 == getChildCount()) {
|
||||
return null;
|
||||
}
|
||||
final float directionDistance = getScrollDirection(targetPosition);
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
final int direction = (int) -Math.signum(directionDistance);
|
||||
|
||||
if (HORIZONTAL == mOrientation) {
|
||||
return new PointF(direction, 0);
|
||||
} else {
|
||||
return new PointF(0, direction);
|
||||
}
|
||||
}
|
||||
|
||||
private float getScrollDirection(final int targetPosition) {
|
||||
final float currentScrollPosition = makeScrollPositionInRange0ToCount(getCurrentScrollPosition(), mItemsCount);
|
||||
|
||||
if (mCircleLayout) {
|
||||
final float t1 = currentScrollPosition - targetPosition;
|
||||
final float t2 = Math.abs(t1) - mItemsCount;
|
||||
if (Math.abs(t1) > Math.abs(t2)) {
|
||||
return Math.signum(t1) * t2;
|
||||
} else {
|
||||
return t1;
|
||||
}
|
||||
} else {
|
||||
return currentScrollPosition - targetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scrollVerticallyBy(final int dy, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
|
||||
if (HORIZONTAL == mOrientation) {
|
||||
return 0;
|
||||
}
|
||||
return scrollBy(dy, recycler, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scrollHorizontallyBy(final int dx, final RecyclerView.Recycler recycler, final RecyclerView.State state) {
|
||||
if (VERTICAL == mOrientation) {
|
||||
return 0;
|
||||
}
|
||||
return scrollBy(dx, recycler, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from {@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)} and
|
||||
* {@link #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)} to calculate needed scroll that is allowed. <br />
|
||||
* <br />
|
||||
* This method may do relayout work.
|
||||
*
|
||||
* @param diff 要滚动的距离
|
||||
* @param recycler 回收期
|
||||
* @param state Transient state of RecyclerView
|
||||
* @return distance that we actually scrolled by
|
||||
*/
|
||||
@CallSuper
|
||||
protected int scrollBy(final int diff, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
|
||||
if (null == mDecoratedChildWidth || null == mDecoratedChildHeight) {
|
||||
return 0;
|
||||
}
|
||||
if (0 == getChildCount() || 0 == diff) {
|
||||
return 0;
|
||||
}
|
||||
final int resultScroll;
|
||||
if (mCircleLayout) {
|
||||
resultScroll = diff;
|
||||
|
||||
mLayoutHelper.mScrollOffset += resultScroll;
|
||||
|
||||
final int maxOffset = getScrollItemSize() * mItemsCount;
|
||||
while (0 > mLayoutHelper.mScrollOffset) {
|
||||
mLayoutHelper.mScrollOffset += maxOffset;
|
||||
}
|
||||
while (mLayoutHelper.mScrollOffset > maxOffset) {
|
||||
mLayoutHelper.mScrollOffset -= maxOffset;
|
||||
}
|
||||
|
||||
mLayoutHelper.mScrollOffset -= resultScroll;
|
||||
} else {
|
||||
final int maxOffset = getMaxScrollOffset();
|
||||
|
||||
if (0 > mLayoutHelper.mScrollOffset + diff) {
|
||||
resultScroll = -mLayoutHelper.mScrollOffset; //to make it 0
|
||||
} else if (mLayoutHelper.mScrollOffset + diff > maxOffset) {
|
||||
resultScroll = maxOffset - mLayoutHelper.mScrollOffset; //to make it maxOffset
|
||||
} else {
|
||||
resultScroll = diff;
|
||||
}
|
||||
}
|
||||
if (0 != resultScroll) {
|
||||
mLayoutHelper.mScrollOffset += resultScroll;
|
||||
fillData(recycler, state);
|
||||
}
|
||||
return resultScroll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeasure(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state, final int widthSpec, final int heightSpec) {
|
||||
mDecoratedChildSizeInvalid = true;
|
||||
|
||||
super.onMeasure(recycler, state, widthSpec, heightSpec);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void onAdapterChanged(final RecyclerView.Adapter oldAdapter, final RecyclerView.Adapter newAdapter) {
|
||||
super.onAdapterChanged(oldAdapter, newAdapter);
|
||||
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("RefusedBequest")
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
|
||||
if (0 == state.getItemCount()) {
|
||||
removeAndRecycleAllViews(recycler);
|
||||
selectItemCenterPosition(INVALID_POSITION);
|
||||
return;
|
||||
}
|
||||
|
||||
detachAndScrapAttachedViews(recycler);
|
||||
|
||||
if (null == mDecoratedChildWidth || mDecoratedChildSizeInvalid) {
|
||||
final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
|
||||
|
||||
final boolean shouldRecycle;
|
||||
final View view;
|
||||
if (scrapList.isEmpty()) {
|
||||
shouldRecycle = true;
|
||||
final int itemsCount = state.getItemCount();
|
||||
view = recycler.getViewForPosition(
|
||||
mPendingScrollPosition == INVALID_POSITION ?
|
||||
0 :
|
||||
Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition))
|
||||
);
|
||||
addView(view);
|
||||
} else {
|
||||
shouldRecycle = false;
|
||||
view = scrapList.get(0).itemView;
|
||||
}
|
||||
measureChildWithMargins(view, 0, 0);
|
||||
|
||||
final int decoratedChildWidth = getDecoratedMeasuredWidth(view);
|
||||
final int decoratedChildHeight = getDecoratedMeasuredHeight(view);
|
||||
if (shouldRecycle) {
|
||||
detachAndScrapView(view, recycler);
|
||||
}
|
||||
|
||||
if (null != mDecoratedChildWidth && (mDecoratedChildWidth != decoratedChildWidth || mDecoratedChildHeight != decoratedChildHeight)) {
|
||||
if (INVALID_POSITION == mPendingScrollPosition && null == mPendingCarouselSavedState) {
|
||||
mPendingScrollPosition = mCenterItemPosition;
|
||||
}
|
||||
}
|
||||
|
||||
mDecoratedChildWidth = decoratedChildWidth;
|
||||
mDecoratedChildHeight = decoratedChildHeight;
|
||||
mDecoratedChildSizeInvalid = false;
|
||||
}
|
||||
|
||||
if (INVALID_POSITION != mPendingScrollPosition) {
|
||||
final int itemsCount = state.getItemCount();
|
||||
mPendingScrollPosition = 0 == itemsCount ? INVALID_POSITION : Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition));
|
||||
}
|
||||
if (INVALID_POSITION != mPendingScrollPosition) {
|
||||
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingScrollPosition, state);
|
||||
mPendingScrollPosition = INVALID_POSITION;
|
||||
mPendingCarouselSavedState = null;
|
||||
} else if (null != mPendingCarouselSavedState) {
|
||||
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingCarouselSavedState.mCenterItemPosition, state);
|
||||
mPendingCarouselSavedState = null;
|
||||
} else if (state.didStructureChange() && INVALID_POSITION != mCenterItemPosition) {
|
||||
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mCenterItemPosition, state);
|
||||
}
|
||||
|
||||
fillData(recycler, state);
|
||||
}
|
||||
|
||||
private int calculateScrollForSelectingPosition(final int itemPosition, final RecyclerView.State state) {
|
||||
if (itemPosition == INVALID_POSITION) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int fixedItemPosition = itemPosition < state.getItemCount() ? itemPosition : state.getItemCount() - 1;
|
||||
return fixedItemPosition * (VERTICAL == mOrientation ? mDecoratedChildHeight : mDecoratedChildWidth);
|
||||
}
|
||||
|
||||
private void fillData(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
|
||||
final float currentScrollPosition = getCurrentScrollPosition();
|
||||
|
||||
generateLayoutOrder(currentScrollPosition, state);
|
||||
detachAndScrapAttachedViews(recycler);
|
||||
recyclerOldViews(recycler);
|
||||
|
||||
final int width = getWidthNoPadding();
|
||||
final int height = getHeightNoPadding();
|
||||
if (VERTICAL == mOrientation) {
|
||||
fillDataVertical(recycler, width, height);
|
||||
} else {
|
||||
fillDataHorizontal(recycler, width, height);
|
||||
}
|
||||
|
||||
recycler.clear();
|
||||
|
||||
detectOnItemSelectionChanged(currentScrollPosition, state);
|
||||
}
|
||||
|
||||
private void detectOnItemSelectionChanged(final float currentScrollPosition, final RecyclerView.State state) {
|
||||
final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, state.getItemCount());
|
||||
final int centerItem = Math.round(absCurrentScrollPosition);
|
||||
|
||||
if (mCenterItemPosition != centerItem) {
|
||||
mCenterItemPosition = centerItem;
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
selectItemCenterPosition(centerItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void selectItemCenterPosition(final int centerItem) {
|
||||
for (final OnCenterItemSelectionListener onCenterItemSelectionListener : mOnCenterItemSelectionListeners) {
|
||||
onCenterItemSelectionListener.onCenterItemChanged(centerItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDataVertical(final RecyclerView.Recycler recycler, final int width, final int height) {
|
||||
final int start = (width - mDecoratedChildWidth) / 2;
|
||||
final int end = start + mDecoratedChildWidth;
|
||||
|
||||
final int centerViewTop = (height - mDecoratedChildHeight) / 2;
|
||||
|
||||
for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
|
||||
final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
|
||||
final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff);
|
||||
final int top = centerViewTop + offset;
|
||||
final int bottom = top + mDecoratedChildHeight;
|
||||
fillChildItem(start, top, end, bottom, layoutOrder, recycler, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDataHorizontal(final RecyclerView.Recycler recycler, final int width, final int height) {
|
||||
final int top = (height - mDecoratedChildHeight) / 2;
|
||||
final int bottom = top + mDecoratedChildHeight;
|
||||
|
||||
final int centerViewStart = (width - mDecoratedChildWidth) / 2;
|
||||
|
||||
for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
|
||||
final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
|
||||
final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff);
|
||||
final int start = centerViewStart + offset;
|
||||
final int end = start + mDecoratedChildWidth;
|
||||
fillChildItem(start, top, end, bottom, layoutOrder, recycler, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("MethodWithTooManyParameters")
|
||||
private void fillChildItem(final int start, final int top, final int end, final int bottom, @NonNull final LayoutOrder layoutOrder, @NonNull final RecyclerView.Recycler recycler, final int i) {
|
||||
final View view = bindChild(layoutOrder.mItemAdapterPosition, recycler);
|
||||
ViewCompat.setElevation(view, i);
|
||||
ItemTransformation transformation = null;
|
||||
if (null != mViewPostLayout) {
|
||||
transformation = mViewPostLayout.transformChild(view, layoutOrder.mItemPositionDiff, mOrientation, layoutOrder.mItemAdapterPosition);
|
||||
}
|
||||
if (null == transformation) {
|
||||
view.layout(start, top, end, bottom);
|
||||
} else {
|
||||
view.layout(Math.round(start + transformation.mTranslationX), Math.round(top + transformation.mTranslationY),
|
||||
Math.round(end + transformation.mTranslationX), Math.round(bottom + transformation.mTranslationY));
|
||||
view.setScaleX(transformation.mScaleX);
|
||||
view.setScaleY(transformation.mScaleY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中心项目的当前滚动位置。如果是循环布局,则该值可以在任何范围内。如果不是,那么它在[0,-1]
|
||||
*/
|
||||
private float getCurrentScrollPosition() {
|
||||
final int fullScrollSize = getMaxScrollOffset();
|
||||
if (0 == fullScrollSize) {
|
||||
return 0;
|
||||
}
|
||||
return 1.0f * mLayoutHelper.mScrollOffset / getScrollItemSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充布局中所有项目的最大滚动值。通常,这仅适用于非循环布局。
|
||||
*/
|
||||
private int getMaxScrollOffset() {
|
||||
return getScrollItemSize() * (mItemsCount - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Because we can support old Android versions, we should layout our children in specific order to make our center view in the top of layout
|
||||
* (this item should layout last). So this method will calculate layout order and fill up {@link #mLayoutHelper} object.
|
||||
* This object will be filled by only needed to layout items. Non visible items will not be there.
|
||||
*
|
||||
* @param currentScrollPosition current scroll position this is a value that indicates position of center item
|
||||
* (if this value is int, then center item is really in the center of the layout, else it is near state).
|
||||
* Be aware that this value can be in any range is it is cycle layout
|
||||
* @param state Transient state of RecyclerView
|
||||
* @see #getCurrentScrollPosition()
|
||||
*/
|
||||
private void generateLayoutOrder(final float currentScrollPosition, @NonNull final RecyclerView.State state) {
|
||||
mItemsCount = state.getItemCount();
|
||||
final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, mItemsCount);
|
||||
final int centerItem = Math.round(absCurrentScrollPosition);
|
||||
|
||||
if (mCircleLayout && 1 < mItemsCount) {
|
||||
final int layoutCount = Math.min(mLayoutHelper.mMaxVisibleItems * 2 + 1, mItemsCount);
|
||||
|
||||
mLayoutHelper.initLayoutOrder(layoutCount);
|
||||
|
||||
final int countLayoutHalf = layoutCount / 2;
|
||||
// before center item
|
||||
for (int i = 1; i <= countLayoutHalf; ++i) {
|
||||
final int position = Math.round(absCurrentScrollPosition - i + mItemsCount) % mItemsCount;
|
||||
mLayoutHelper.setLayoutOrder(countLayoutHalf - i, position, centerItem - absCurrentScrollPosition - i);
|
||||
}
|
||||
// after center item
|
||||
for (int i = layoutCount - 1; i >= countLayoutHalf + 1; --i) {
|
||||
final int position = Math.round(absCurrentScrollPosition - i + layoutCount) % mItemsCount;
|
||||
mLayoutHelper.setLayoutOrder(i - 1, position, centerItem - absCurrentScrollPosition + layoutCount - i);
|
||||
}
|
||||
mLayoutHelper.setLayoutOrder(layoutCount - 1, centerItem, centerItem - absCurrentScrollPosition);
|
||||
|
||||
} else {
|
||||
final int firstVisible = Math.max(centerItem - mLayoutHelper.mMaxVisibleItems, 0);
|
||||
final int lastVisible = Math.min(centerItem + mLayoutHelper.mMaxVisibleItems, mItemsCount - 1);
|
||||
final int layoutCount = lastVisible - firstVisible + 1;
|
||||
|
||||
mLayoutHelper.initLayoutOrder(layoutCount);
|
||||
|
||||
for (int i = firstVisible; i <= lastVisible; ++i) {
|
||||
if (i == centerItem) {
|
||||
mLayoutHelper.setLayoutOrder(layoutCount - 1, i, i - absCurrentScrollPosition);
|
||||
} else if (i < centerItem) {
|
||||
mLayoutHelper.setLayoutOrder(i - firstVisible, i, i - absCurrentScrollPosition);
|
||||
} else {
|
||||
mLayoutHelper.setLayoutOrder(layoutCount - (i - centerItem) - 1, i, i - absCurrentScrollPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getWidthNoPadding() {
|
||||
return getWidth() - getPaddingStart() - getPaddingEnd();
|
||||
}
|
||||
|
||||
public int getHeightNoPadding() {
|
||||
return getHeight() - getPaddingEnd() - getPaddingStart();
|
||||
}
|
||||
|
||||
private View bindChild(final int position, @NonNull final RecyclerView.Recycler recycler) {
|
||||
final View view = recycler.getViewForPosition(position);
|
||||
|
||||
addView(view);
|
||||
measureChildWithMargins(view, 0, 0);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void recyclerOldViews(final RecyclerView.Recycler recycler) {
|
||||
for (RecyclerView.ViewHolder viewHolder : new ArrayList<>(recycler.getScrapList())) {
|
||||
int adapterPosition = viewHolder.getAdapterPosition();
|
||||
boolean found = false;
|
||||
for (LayoutOrder layoutOrder : mLayoutHelper.mLayoutOrder) {
|
||||
if (layoutOrder.mItemAdapterPosition == adapterPosition) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
recycler.recycleView(viewHolder.itemView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)} to calculate item offset from layout center line. <br />
|
||||
* <br />
|
||||
* Returns {@link #convertItemPositionDiffToSmoothPositionDiff(float)} * (size off area above center item when it is on the center). <br />
|
||||
* Sign is: plus if this item is bellow center line, minus if not<br />
|
||||
* <br />
|
||||
* ----- - area above it<br />
|
||||
* ||||| - center item<br />
|
||||
* ----- - area bellow it (it has the same size as are above center item)<br />
|
||||
*
|
||||
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
|
||||
* if this is 1 then this item is bellow the layout center line in the full item size distance.
|
||||
* @return offset in scroll px coordinates.
|
||||
*/
|
||||
protected int getCardOffsetByPositionDiff(final float itemPositionDiff) {
|
||||
final double smoothPosition = convertItemPositionDiffToSmoothPositionDiff(itemPositionDiff);
|
||||
|
||||
final int dimenDiff;
|
||||
if (VERTICAL == mOrientation) {
|
||||
dimenDiff = (getHeightNoPadding() - mDecoratedChildHeight) / 2;
|
||||
} else {
|
||||
dimenDiff = (getWidthNoPadding() - mDecoratedChildWidth) / 2;
|
||||
}
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
return (int) Math.round(Math.signum(itemPositionDiff) * dimenDiff * smoothPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during {@link #getCardOffsetByPositionDiff(float)} for better item movement. <br/>
|
||||
* Current implementation speed up items that are far from layout center line and slow down items that are close to this line.
|
||||
* This code is full of maths. If you want to make items move in a different way, probably you should override this method.<br />
|
||||
* Please see code comments for better explanations.
|
||||
*
|
||||
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
|
||||
* if this is 1 then this item is bellow the layout center line in the full item size distance.
|
||||
* @return smooth position offset. needed for scroll calculation and better user experience.
|
||||
* @see #getCardOffsetByPositionDiff(float)
|
||||
*/
|
||||
@SuppressWarnings({"MagicNumber", "InstanceMethodNamingConvention"})
|
||||
protected double convertItemPositionDiffToSmoothPositionDiff(final float itemPositionDiff) {
|
||||
// generally item moves the same way above center and bellow it. So we don't care about diff sign.
|
||||
final float absIemPositionDiff = Math.abs(itemPositionDiff);
|
||||
|
||||
// we detect if this item is close for center or not. We use (1 / maxVisibleItem) ^ (1/3) as close definer.
|
||||
if (absIemPositionDiff > StrictMath.pow(1.0f / mLayoutHelper.mMaxVisibleItems, 1.0f / 3)) {
|
||||
// this item is far from center line, so we should make it move like square root function
|
||||
return StrictMath.pow(absIemPositionDiff / mLayoutHelper.mMaxVisibleItems, 1 / 2.0f);
|
||||
} else {
|
||||
// this item is close from center line. we should slow it down and don't make it speed up very quick.
|
||||
// so square function in range of [0, (1/maxVisible)^(1/3)] is quite good in it;
|
||||
return StrictMath.pow(absIemPositionDiff, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return full item size
|
||||
*/
|
||||
protected int getScrollItemSize() {
|
||||
if (VERTICAL == mOrientation) {
|
||||
return mDecoratedChildHeight;
|
||||
} else {
|
||||
return mDecoratedChildWidth;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
if (null != mPendingCarouselSavedState) {
|
||||
return new CarouselSavedState(mPendingCarouselSavedState);
|
||||
}
|
||||
final CarouselSavedState savedState = new CarouselSavedState(super.onSaveInstanceState());
|
||||
savedState.mCenterItemPosition = mCenterItemPosition;
|
||||
return savedState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(final Parcelable state) {
|
||||
if (state instanceof CarouselSavedState) {
|
||||
mPendingCarouselSavedState = (CarouselSavedState) state;
|
||||
|
||||
super.onRestoreInstanceState(mPendingCarouselSavedState.mSuperState);
|
||||
} else {
|
||||
super.onRestoreInstanceState(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 从中心到最近项目的滚动偏移量
|
||||
*/
|
||||
protected int getOffsetCenterView() {
|
||||
return Math.round(getCurrentScrollPosition()) * getScrollItemSize() - mLayoutHelper.mScrollOffset;
|
||||
}
|
||||
|
||||
protected int getOffsetForCurrentView(@NonNull final View view) {
|
||||
final int targetPosition = getPosition(view);
|
||||
final float directionDistance = getScrollDirection(targetPosition);
|
||||
|
||||
return Math.round(directionDistance * getScrollItemSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使滚动范围在[0,count]内的Helper方法。通常,只有循环布局才需要此方法。
|
||||
*
|
||||
* @param currentScrollPosition 滚动位置范围 个位数 view的index 小数滚动的范围
|
||||
* @param count adapter 中的数量
|
||||
* @return 在[0,总数]范围内滚动位置良好
|
||||
*/
|
||||
private static float makeScrollPositionInRange0ToCount(final float currentScrollPosition, final int count) {
|
||||
float absCurrentScrollPosition = currentScrollPosition;
|
||||
while (0 > absCurrentScrollPosition) {
|
||||
absCurrentScrollPosition += count;
|
||||
}
|
||||
while (Math.round(absCurrentScrollPosition) >= count) {
|
||||
absCurrentScrollPosition -= count;
|
||||
}
|
||||
return absCurrentScrollPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface methods will be called for each visible view item after general LayoutManager layout finishes. <br />
|
||||
* <br />
|
||||
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
|
||||
*/
|
||||
@SuppressWarnings("InterfaceNeverImplemented")
|
||||
public abstract static class PostLayoutListener {
|
||||
|
||||
/**
|
||||
* 子布局完成后调用。通常,您可以在这里进行任何平移和缩放工作。
|
||||
*
|
||||
* @param child view that was layout
|
||||
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
|
||||
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
|
||||
* @param itemPositionInAdapter item position inside adapter for this layout pass
|
||||
*/
|
||||
public ItemTransformation transformChild(
|
||||
@NonNull final View child,
|
||||
final float itemPositionToCenterDiff,
|
||||
final int orientation,
|
||||
final int itemPositionInAdapter
|
||||
) {
|
||||
return transformChild(child, itemPositionToCenterDiff, orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after child layout finished. Generally you can do any translation and scaling work here.
|
||||
*
|
||||
* @param child view that was layout
|
||||
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
|
||||
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
|
||||
*/
|
||||
public ItemTransformation transformChild(
|
||||
@NonNull final View child,
|
||||
final float itemPositionToCenterDiff,
|
||||
final int orientation
|
||||
) {
|
||||
throw new IllegalStateException("at least one transformChild should be implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnCenterItemSelectionListener {
|
||||
|
||||
/**
|
||||
* Listener that will be called on every change of center item.
|
||||
* This listener will be triggered on <b>every</b> layout operation if item was changed.
|
||||
* Do not do any expensive operations in this method since this will effect scroll experience.
|
||||
*
|
||||
* @param adapterPosition current layout center item
|
||||
*/
|
||||
void onCenterItemChanged(final int adapterPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that holds currently visible items.
|
||||
* Generally this class fills this list. <br />
|
||||
* <br />
|
||||
* This class holds all scroll and maxVisible items state.
|
||||
*
|
||||
* @see #getMaxVisibleItems()
|
||||
*/
|
||||
private static class LayoutHelper {
|
||||
|
||||
private int mMaxVisibleItems;
|
||||
|
||||
private int mScrollOffset;
|
||||
|
||||
private LayoutOrder[] mLayoutOrder;
|
||||
|
||||
private final List<WeakReference<LayoutOrder>> mReusedItems = new ArrayList<>();
|
||||
|
||||
LayoutHelper(final int maxVisibleItems) {
|
||||
mMaxVisibleItems = maxVisibleItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before any fill calls. Needed to recycle old items and init new array list. Generally this list is an array an it is reused.
|
||||
*
|
||||
* @param layoutCount items count that will be layout
|
||||
*/
|
||||
void initLayoutOrder(final int layoutCount) {
|
||||
if (null == mLayoutOrder || mLayoutOrder.length != layoutCount) {
|
||||
if (null != mLayoutOrder) {
|
||||
recycleItems(mLayoutOrder);
|
||||
}
|
||||
mLayoutOrder = new LayoutOrder[layoutCount];
|
||||
fillLayoutOrder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during layout generation process of filling this list. Should be called only after {@link #initLayoutOrder(int)} method call.
|
||||
*
|
||||
* @param arrayPosition position in layout order
|
||||
* @param itemAdapterPosition adapter position of item for future data filling logic
|
||||
* @param itemPositionDiff difference of current item scroll position and center item position.
|
||||
* if this is a center item and it is in real center of layout, then this will be 0.
|
||||
* if current layout is not in the center, then this value will never be int.
|
||||
* if this item center is bellow layout center line then this value is greater then 0,
|
||||
* else less then 0.
|
||||
*/
|
||||
void setLayoutOrder(final int arrayPosition, final int itemAdapterPosition, final float itemPositionDiff) {
|
||||
final LayoutOrder item = mLayoutOrder[arrayPosition];
|
||||
item.mItemAdapterPosition = itemAdapterPosition;
|
||||
item.mItemPositionDiff = itemPositionDiff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks is this screen Layout has this adapterPosition view in layout
|
||||
*
|
||||
* @param adapterPosition adapter position of item for future data filling logic
|
||||
* @return true is adapterItem is in layout
|
||||
*/
|
||||
boolean hasAdapterPosition(final int adapterPosition) {
|
||||
if (null != mLayoutOrder) {
|
||||
for (final LayoutOrder layoutOrder : mLayoutOrder) {
|
||||
if (layoutOrder.mItemAdapterPosition == adapterPosition) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("VariableArgumentMethod")
|
||||
private void recycleItems(@NonNull final LayoutOrder... layoutOrders) {
|
||||
for (final LayoutOrder layoutOrder : layoutOrders) {
|
||||
//noinspection ObjectAllocationInLoop
|
||||
mReusedItems.add(new WeakReference<>(layoutOrder));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillLayoutOrder() {
|
||||
for (int i = 0, length = mLayoutOrder.length; i < length; ++i) {
|
||||
if (null == mLayoutOrder[i]) {
|
||||
mLayoutOrder[i] = createLayoutOrder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LayoutOrder createLayoutOrder() {
|
||||
final Iterator<WeakReference<LayoutOrder>> iterator = mReusedItems.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final WeakReference<LayoutOrder> layoutOrderWeakReference = iterator.next();
|
||||
final LayoutOrder layoutOrder = layoutOrderWeakReference.get();
|
||||
iterator.remove();
|
||||
if (null != layoutOrder) {
|
||||
return layoutOrder;
|
||||
}
|
||||
}
|
||||
return new LayoutOrder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that holds item data.
|
||||
* This class is filled during {@link #generateLayoutOrder(float, RecyclerView.State)} and used during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)}
|
||||
*/
|
||||
private static class LayoutOrder {
|
||||
|
||||
/**
|
||||
* Item adapter position
|
||||
*/
|
||||
private int mItemAdapterPosition;
|
||||
/**
|
||||
* Item center difference to layout center. If center of item is bellow layout center, then this value is greater then 0, else it is less.
|
||||
*/
|
||||
private float mItemPositionDiff;
|
||||
}
|
||||
|
||||
protected static class CarouselSavedState implements Parcelable {
|
||||
|
||||
private final Parcelable mSuperState;
|
||||
private int mCenterItemPosition;
|
||||
|
||||
protected CarouselSavedState(@Nullable final Parcelable superState) {
|
||||
mSuperState = superState;
|
||||
}
|
||||
|
||||
private CarouselSavedState(@NonNull final Parcel in) {
|
||||
mSuperState = in.readParcelable(Parcelable.class.getClassLoader());
|
||||
mCenterItemPosition = in.readInt();
|
||||
}
|
||||
|
||||
protected CarouselSavedState(@NonNull final CarouselSavedState other) {
|
||||
mSuperState = other.mSuperState;
|
||||
mCenterItemPosition = other.mCenterItemPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel parcel, final int i) {
|
||||
parcel.writeParcelable(mSuperState, i);
|
||||
parcel.writeInt(mCenterItemPosition);
|
||||
}
|
||||
|
||||
public static final Creator<CarouselSavedState> CREATOR
|
||||
= new Creator<CarouselSavedState>() {
|
||||
@Override
|
||||
public CarouselSavedState createFromParcel(final Parcel parcel) {
|
||||
return new CarouselSavedState(parcel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CarouselSavedState[] newArray(final int i) {
|
||||
return new CarouselSavedState[i];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Implementation of {@link CarouselLayoutManager.PostLayoutListener} that makes interesting scaling of items. <br />
|
||||
* We are trying to make items scaling quicker for closer items for center and slower for when they are far away.<br />
|
||||
* Tis implementation uses atan function for this purpose.
|
||||
*/
|
||||
public class CarouselZoomPostLayoutListener extends CarouselLayoutManager.PostLayoutListener {
|
||||
|
||||
private final float mScaleMultiplier;
|
||||
|
||||
public CarouselZoomPostLayoutListener() {
|
||||
this(0.21f);
|
||||
}
|
||||
|
||||
public CarouselZoomPostLayoutListener(final float scaleMultiplier) {
|
||||
mScaleMultiplier = scaleMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemTransformation transformChild(@NonNull final View child, final float itemPositionToCenterDiff, final int orientation) {
|
||||
float scale = 1.0f - mScaleMultiplier * Math.abs(itemPositionToCenterDiff);
|
||||
// float scale;
|
||||
// if(itemPositionToCenterDiff==1||itemPositionToCenterDiff==-1){
|
||||
// scale = 0.79f;
|
||||
// }else {
|
||||
// scale = 1.0f - mScaleMultiplier * Math.abs(itemPositionToCenterDiff);
|
||||
// if(scale<0.79){
|
||||
// scale = 0.79f;
|
||||
// }
|
||||
// }
|
||||
// because scaling will make view smaller in its center, then we should move this item to the top or bottom to make it visible
|
||||
final float translateY;
|
||||
final float translateX;
|
||||
if (CarouselLayoutManager.VERTICAL == orientation) {
|
||||
final float translateYGeneral = child.getMeasuredHeight() * (1 - scale) / 2f;
|
||||
translateY = Math.signum(itemPositionToCenterDiff) * translateYGeneral;
|
||||
translateX = 0;
|
||||
} else {
|
||||
final float translateXGeneral = child.getMeasuredWidth() * (1 - scale) / 2f;
|
||||
translateX = Math.signum(itemPositionToCenterDiff) * translateXGeneral;
|
||||
translateY = 0;
|
||||
}
|
||||
System.err.println("CarouselZoomPostLayoutListener---itemPositionToCenterDiff:"+itemPositionToCenterDiff);
|
||||
|
||||
return new ItemTransformation(scale, scale, translateX, translateY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Class for centering items after scroll event.<br />
|
||||
* This class will listen to current scroll state and if item is not centered after scroll it will automatically scroll it to center.
|
||||
*/
|
||||
public class CenterScrollListener extends RecyclerView.OnScrollListener {
|
||||
|
||||
private boolean mAutoSet = true;
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
if (!(layoutManager instanceof CarouselLayoutManager)) {
|
||||
mAutoSet = true;
|
||||
return;
|
||||
}
|
||||
|
||||
final CarouselLayoutManager lm = (CarouselLayoutManager) layoutManager;
|
||||
if (!mAutoSet) {
|
||||
if (RecyclerView.SCROLL_STATE_IDLE == newState) {
|
||||
final int scrollNeeded = lm.getOffsetCenterView();
|
||||
// 滚动到中心位置
|
||||
if (CarouselLayoutManager.HORIZONTAL == lm.getOrientation()) {
|
||||
recyclerView.smoothScrollBy(scrollNeeded, 0);
|
||||
} else {
|
||||
recyclerView.smoothScrollBy(0, scrollNeeded);
|
||||
}
|
||||
pageSelect(recyclerView,newState);
|
||||
mAutoSet = true;
|
||||
}
|
||||
}
|
||||
if (RecyclerView.SCROLL_STATE_DRAGGING == newState || RecyclerView.SCROLL_STATE_SETTLING == newState) {
|
||||
mAutoSet = false;
|
||||
}
|
||||
if(RecyclerView.SCROLL_STATE_DRAGGING == newState){
|
||||
pageStop();
|
||||
}
|
||||
}
|
||||
|
||||
protected void pageStop() {
|
||||
|
||||
}
|
||||
|
||||
protected void pageSelect(RecyclerView recyclerView, final int newState) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class DefaultChildSelectionListener extends CarouselChildSelectionListener {
|
||||
|
||||
@NonNull
|
||||
private final OnCenterItemClickListener mOnCenterItemClickListener;
|
||||
|
||||
protected DefaultChildSelectionListener(@NonNull final OnCenterItemClickListener onCenterItemClickListener, @NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager) {
|
||||
super(recyclerView, carouselLayoutManager);
|
||||
|
||||
mOnCenterItemClickListener = onCenterItemClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager, @NonNull final View v) {
|
||||
mOnCenterItemClickListener.onCenterItemClicked(recyclerView, carouselLayoutManager, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager, @NonNull final View v) {
|
||||
recyclerView.smoothScrollToPosition(carouselLayoutManager.getPosition(v));
|
||||
}
|
||||
|
||||
public static DefaultChildSelectionListener initCenterItemListener(@NonNull final OnCenterItemClickListener onCenterItemClickListener, @NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager) {
|
||||
return new DefaultChildSelectionListener(onCenterItemClickListener, recyclerView, carouselLayoutManager);
|
||||
}
|
||||
|
||||
public interface OnCenterItemClickListener {
|
||||
|
||||
void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManager carouselLayoutManager, @NonNull final View v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
public class ItemTransformation {
|
||||
|
||||
final float mScaleX;
|
||||
final float mScaleY;
|
||||
final float mTranslationX;
|
||||
final float mTranslationY;
|
||||
|
||||
public ItemTransformation(final float scaleX, final float scaleY, final float translationX, final float translationY) {
|
||||
mScaleX = scaleX;
|
||||
mScaleY = scaleY;
|
||||
mTranslationX = translationX;
|
||||
mTranslationY = translationY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,974 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A cache that uses a bounded amount of space on a filesystem. Each cache
|
||||
* entry has a string key and a fixed number of values. Each key must match
|
||||
* the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
|
||||
* accessible as streams or files. Each value must be between {@code 0} and
|
||||
* {@code Integer.MAX_VALUE} bytes in length.
|
||||
*
|
||||
* <p>The cache stores its data in a directory on the filesystem. This
|
||||
* directory must be exclusive to the cache; the cache may delete or overwrite
|
||||
* files from its directory. It is an error for multiple processes to use the
|
||||
* same cache directory at the same time.
|
||||
*
|
||||
* <p>This cache limits the number of bytes that it will store on the
|
||||
* filesystem. When the number of stored bytes exceeds the limit, the cache will
|
||||
* remove entries in the background until the limit is satisfied. The limit is
|
||||
* not strict: the cache may temporarily exceed it while waiting for files to be
|
||||
* deleted. The limit does not include filesystem overhead or the cache
|
||||
* journal so space-sensitive applications should set a conservative limit.
|
||||
*
|
||||
* <p>Clients call {@link #edit} to create or update the values of an entry. An
|
||||
* entry may have only one editor at one time; if a value is not available to be
|
||||
* edited then {@link #edit} will return null.
|
||||
* <ul>
|
||||
* <li>When an entry is being <strong>created</strong> it is necessary to
|
||||
* supply a full set of values; the empty value should be used as a
|
||||
* placeholder if necessary.
|
||||
* <li>When an entry is being <strong>edited</strong>, it is not necessary
|
||||
* to supply data for every value; values default to their previous
|
||||
* value.
|
||||
* </ul>
|
||||
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
|
||||
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
|
||||
* of values as they were before or after the commit, but never a mix of values.
|
||||
*
|
||||
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
|
||||
* observe the value at the time that {@link #get} was called. Updates and
|
||||
* removals after the call do not impact ongoing reads.
|
||||
*
|
||||
* <p>This class is tolerant of some I/O errors. If files are missing from the
|
||||
* filesystem, the corresponding entries will be dropped from the cache. If
|
||||
* an error occurs while writing a cache value, the edit will fail silently.
|
||||
* Callers should handle other problems by catching {@code IOException} and
|
||||
* responding appropriately.
|
||||
*/
|
||||
final class DiskLruCache implements Closeable {
|
||||
static final String JOURNAL_FILE = "journal";
|
||||
static final String JOURNAL_FILE_TEMP = "journal.tmp";
|
||||
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
|
||||
static final String MAGIC = "libcore.io.DiskLruCache";
|
||||
static final String VERSION_1 = "1";
|
||||
static final long ANY_SEQUENCE_NUMBER = -1;
|
||||
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
|
||||
private static final String CLEAN = "CLEAN";
|
||||
private static final String DIRTY = "DIRTY";
|
||||
private static final String REMOVE = "REMOVE";
|
||||
private static final String READ = "READ";
|
||||
|
||||
/*
|
||||
* This cache uses a journal file named "journal". A typical journal file
|
||||
* looks like this:
|
||||
* libcore.io.DiskLruCache
|
||||
* 1
|
||||
* 100
|
||||
* 2
|
||||
*
|
||||
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
|
||||
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
|
||||
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
|
||||
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
|
||||
* DIRTY 1ab96a171faeeee38496d8b330771a7a
|
||||
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
|
||||
* READ 335c4c6028171cfddfbaae1a9c313c52
|
||||
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
|
||||
*
|
||||
* The first five lines of the journal form its header. They are the
|
||||
* constant string "libcore.io.DiskLruCache", the disk cache's version,
|
||||
* the application's version, the value count, and a blank line.
|
||||
*
|
||||
* Each of the subsequent lines in the file is a record of the state of a
|
||||
* cache entry. Each line contains space-separated values: a state, a key,
|
||||
* and optional state-specific values.
|
||||
* o DIRTY lines track that an entry is actively being created or updated.
|
||||
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
|
||||
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
|
||||
* temporary files may need to be deleted.
|
||||
* o CLEAN lines track a cache entry that has been successfully published
|
||||
* and may be read. A publish line is followed by the lengths of each of
|
||||
* its values.
|
||||
* o READ lines track accesses for LRU.
|
||||
* o REMOVE lines track entries that have been deleted.
|
||||
*
|
||||
* The journal file is appended to as cache operations occur. The journal may
|
||||
* occasionally be compacted by dropping redundant lines. A temporary file named
|
||||
* "journal.tmp" will be used during compaction; that file should be deleted if
|
||||
* it exists when the cache is opened.
|
||||
*/
|
||||
|
||||
private final File directory;
|
||||
private final File journalFile;
|
||||
private final File journalFileTmp;
|
||||
private final File journalFileBackup;
|
||||
private final int appVersion;
|
||||
private long maxSize;
|
||||
private int maxFileCount;
|
||||
private final int valueCount;
|
||||
private long size = 0;
|
||||
private int fileCount = 0;
|
||||
private Writer journalWriter;
|
||||
private final LinkedHashMap<String, Entry> lruEntries =
|
||||
new LinkedHashMap<String, Entry>(0, 0.75f, true);
|
||||
private int redundantOpCount;
|
||||
|
||||
/**
|
||||
* To differentiate between old and current snapshots, each entry is given
|
||||
* a sequence number each time an edit is committed. A snapshot is stale if
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
|
||||
/** This cache uses a single background thread to evict entries. */
|
||||
final ThreadPoolExecutor executorService =
|
||||
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
private final Callable<Void> cleanupCallable = new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
trimToFileCount();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
this.journalFile = new File(directory, JOURNAL_FILE);
|
||||
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
|
||||
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
this.maxFileCount = maxFileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
* there.
|
||||
*
|
||||
* @param directory a writable directory
|
||||
* @param valueCount the number of values per cache entry. Must be positive.
|
||||
* @param maxSize the maximum number of bytes this cache should use to store
|
||||
* @param maxFileCount the maximum file count this cache should store
|
||||
* @throws IOException if reading or writing the cache directory fails
|
||||
*/
|
||||
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount)
|
||||
throws IOException {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
if (maxFileCount <= 0) {
|
||||
throw new IllegalArgumentException("maxFileCount <= 0");
|
||||
}
|
||||
if (valueCount <= 0) {
|
||||
throw new IllegalArgumentException("valueCount <= 0");
|
||||
}
|
||||
|
||||
// If a bkp file exists, use it instead.
|
||||
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
if (backupFile.exists()) {
|
||||
File journalFile = new File(directory, JOURNAL_FILE);
|
||||
// If journal file also exists just delete backup file.
|
||||
if (journalFile.exists()) {
|
||||
backupFile.delete();
|
||||
} else {
|
||||
renameTo(backupFile, journalFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer to pick up where we left off.
|
||||
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
|
||||
if (cache.journalFile.exists()) {
|
||||
try {
|
||||
cache.readJournal();
|
||||
cache.processJournal();
|
||||
cache.journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
|
||||
return cache;
|
||||
} catch (IOException journalIsCorrupt) {
|
||||
System.out
|
||||
.println("DiskLruCache "
|
||||
+ directory
|
||||
+ " is corrupt: "
|
||||
+ journalIsCorrupt.getMessage()
|
||||
+ ", removing");
|
||||
cache.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new empty cache.
|
||||
directory.mkdirs();
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void readJournal() throws IOException {
|
||||
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
|
||||
try {
|
||||
String magic = reader.readLine();
|
||||
String version = reader.readLine();
|
||||
String appVersionString = reader.readLine();
|
||||
String valueCountString = reader.readLine();
|
||||
String blank = reader.readLine();
|
||||
if (!MAGIC.equals(magic)
|
||||
|| !VERSION_1.equals(version)
|
||||
|| !Integer.toString(appVersion).equals(appVersionString)
|
||||
|| !Integer.toString(valueCount).equals(valueCountString)
|
||||
|| !"".equals(blank)) {
|
||||
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
|
||||
+ valueCountString + ", " + blank + "]");
|
||||
}
|
||||
|
||||
int lineCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
readJournalLine(reader.readLine());
|
||||
lineCount++;
|
||||
} catch (EOFException endOfJournal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
redundantOpCount = lineCount - lruEntries.size();
|
||||
} finally {
|
||||
Util.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private void readJournalLine(String line) throws IOException {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
if (firstSpace == -1) {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
|
||||
int keyBegin = firstSpace + 1;
|
||||
int secondSpace = line.indexOf(' ', keyBegin);
|
||||
final String key;
|
||||
if (secondSpace == -1) {
|
||||
key = line.substring(keyBegin);
|
||||
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
|
||||
lruEntries.remove(key);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = line.substring(keyBegin, secondSpace);
|
||||
}
|
||||
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
}
|
||||
|
||||
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
|
||||
String[] parts = line.substring(secondSpace + 1).split(" ");
|
||||
entry.readable = true;
|
||||
entry.currentEditor = null;
|
||||
entry.setLengths(parts);
|
||||
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
|
||||
entry.currentEditor = new Editor(entry);
|
||||
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
|
||||
// This work was already done by calling lruEntries.get().
|
||||
} else {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the initial size and collects garbage as a part of opening the
|
||||
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
|
||||
*/
|
||||
private void processJournal() throws IOException {
|
||||
deleteIfExists(journalFileTmp);
|
||||
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
|
||||
Entry entry = i.next();
|
||||
if (entry.currentEditor == null) {
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
size += entry.lengths[t];
|
||||
fileCount++;
|
||||
}
|
||||
} else {
|
||||
entry.currentEditor = null;
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
deleteIfExists(entry.getCleanFile(t));
|
||||
deleteIfExists(entry.getDirtyFile(t));
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new journal that omits redundant information. This replaces the
|
||||
* current journal if it exists.
|
||||
*/
|
||||
private synchronized void rebuildJournal() throws IOException {
|
||||
if (journalWriter != null) {
|
||||
journalWriter.close();
|
||||
}
|
||||
|
||||
Writer writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
|
||||
try {
|
||||
writer.write(MAGIC);
|
||||
writer.write("\n");
|
||||
writer.write(VERSION_1);
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(appVersion));
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(valueCount));
|
||||
writer.write("\n");
|
||||
writer.write("\n");
|
||||
|
||||
for (Entry entry : lruEntries.values()) {
|
||||
if (entry.currentEditor != null) {
|
||||
writer.write(DIRTY + ' ' + entry.key + '\n');
|
||||
} else {
|
||||
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
if (journalFile.exists()) {
|
||||
renameTo(journalFile, journalFileBackup, true);
|
||||
}
|
||||
renameTo(journalFileTmp, journalFile, false);
|
||||
journalFileBackup.delete();
|
||||
|
||||
journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
}
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
|
||||
* exist is not currently readable. If a value is returned, it is moved to
|
||||
* the head of the LRU queue.
|
||||
*/
|
||||
public synchronized Snapshot get(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Open all streams eagerly to guarantee that we see a single published
|
||||
// snapshot. If we opened streams lazily then the streams could come
|
||||
// from different edits.
|
||||
File[] files = new File[valueCount];
|
||||
InputStream[] ins = new InputStream[valueCount];
|
||||
try {
|
||||
File file;
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
file = entry.getCleanFile(i);
|
||||
files[i] = file;
|
||||
ins[i] = new FileInputStream(file);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// A file must have been deleted manually!
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (ins[i] != null) {
|
||||
Util.closeQuietly(ins[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(READ + ' ' + key + '\n');
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for the entry named {@code key}, or null if another
|
||||
* edit is in progress.
|
||||
*/
|
||||
public Editor edit(String key) throws IOException {
|
||||
return edit(key, ANY_SEQUENCE_NUMBER);
|
||||
}
|
||||
|
||||
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|
||||
|| entry.sequenceNumber != expectedSequenceNumber)) {
|
||||
return null; // Snapshot is stale.
|
||||
}
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
} else if (entry.currentEditor != null) {
|
||||
return null; // Another edit is in progress.
|
||||
}
|
||||
|
||||
Editor editor = new Editor(entry);
|
||||
entry.currentEditor = editor;
|
||||
|
||||
// Flush the journal before creating files to prevent file leaks.
|
||||
journalWriter.write(DIRTY + ' ' + key + '\n');
|
||||
journalWriter.flush();
|
||||
return editor;
|
||||
}
|
||||
|
||||
/** Returns the directory where this cache stores its data. */
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes that this cache should use to store
|
||||
* its data.
|
||||
*/
|
||||
public synchronized long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/** Returns the maximum number of files that this cache should store */
|
||||
public synchronized int getMaxFileCount() {
|
||||
return maxFileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum number of bytes the cache can store and queues a job
|
||||
* to trim the existing store, if necessary.
|
||||
*/
|
||||
public synchronized void setMaxSize(long maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes currently being used to store the values in
|
||||
* this cache. This may be greater than the max size if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of files currently being used to store the values in
|
||||
* this cache. This may be greater than the max file count if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long fileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
|
||||
Entry entry = editor.entry;
|
||||
if (entry.currentEditor != editor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// If this edit is creating the entry for the first time, every index must have a value.
|
||||
if (success && !entry.readable) {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (!editor.written[i]) {
|
||||
editor.abort();
|
||||
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
|
||||
}
|
||||
if (!entry.getDirtyFile(i).exists()) {
|
||||
editor.abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File dirty = entry.getDirtyFile(i);
|
||||
if (success) {
|
||||
if (dirty.exists()) {
|
||||
File clean = entry.getCleanFile(i);
|
||||
dirty.renameTo(clean);
|
||||
long oldLength = entry.lengths[i];
|
||||
long newLength = clean.length();
|
||||
entry.lengths[i] = newLength;
|
||||
size = size - oldLength + newLength;
|
||||
fileCount++;
|
||||
}
|
||||
} else {
|
||||
deleteIfExists(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
entry.currentEditor = null;
|
||||
if (entry.readable | success) {
|
||||
entry.readable = true;
|
||||
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
if (success) {
|
||||
entry.sequenceNumber = nextSequenceNumber++;
|
||||
}
|
||||
} else {
|
||||
lruEntries.remove(entry.key);
|
||||
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
|
||||
}
|
||||
journalWriter.flush();
|
||||
|
||||
if (size > maxSize || fileCount > maxFileCount || journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only rebuild the journal when it will halve the size of the journal
|
||||
* and eliminate at least 2000 ops.
|
||||
*/
|
||||
private boolean journalRebuildRequired() {
|
||||
final int redundantOpCompactThreshold = 2000;
|
||||
return redundantOpCount >= redundantOpCompactThreshold //
|
||||
&& redundantOpCount >= lruEntries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the entry for {@code key} if it exists and can be removed. Entries
|
||||
* actively being edited cannot be removed.
|
||||
*
|
||||
* @return true if an entry was removed.
|
||||
*/
|
||||
public synchronized boolean remove(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null || entry.currentEditor != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File file = entry.getCleanFile(i);
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException("failed to delete " + file);
|
||||
}
|
||||
size -= entry.lengths[i];
|
||||
fileCount--;
|
||||
entry.lengths[i] = 0;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(REMOVE + ' ' + key + '\n');
|
||||
lruEntries.remove(key);
|
||||
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns true if this cache has been closed. */
|
||||
public synchronized boolean isClosed() {
|
||||
return journalWriter == null;
|
||||
}
|
||||
|
||||
private void checkNotClosed() {
|
||||
if (journalWriter == null) {
|
||||
throw new IllegalStateException("cache is closed");
|
||||
}
|
||||
}
|
||||
|
||||
/** Force buffered operations to the filesystem. */
|
||||
public synchronized void flush() throws IOException {
|
||||
checkNotClosed();
|
||||
trimToSize();
|
||||
trimToFileCount();
|
||||
journalWriter.flush();
|
||||
}
|
||||
|
||||
/** Closes this cache. Stored values will remain on the filesystem. */
|
||||
public synchronized void close() throws IOException {
|
||||
if (journalWriter == null) {
|
||||
return; // Already closed.
|
||||
}
|
||||
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
|
||||
if (entry.currentEditor != null) {
|
||||
entry.currentEditor.abort();
|
||||
}
|
||||
}
|
||||
trimToSize();
|
||||
trimToFileCount();
|
||||
journalWriter.close();
|
||||
journalWriter = null;
|
||||
}
|
||||
|
||||
private void trimToSize() throws IOException {
|
||||
while (size > maxSize) {
|
||||
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private void trimToFileCount() throws IOException {
|
||||
while (fileCount > maxFileCount) {
|
||||
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete
|
||||
* all files in the cache directory including files that weren't created by
|
||||
* the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
close();
|
||||
Util.deleteContents(directory);
|
||||
}
|
||||
|
||||
private void validateKey(String key) {
|
||||
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
}
|
||||
|
||||
/** A snapshot of the values for an entry. */
|
||||
public final class Snapshot implements Closeable {
|
||||
private final String key;
|
||||
private final long sequenceNumber;
|
||||
private File[] files;
|
||||
private final InputStream[] ins;
|
||||
private final long[] lengths;
|
||||
|
||||
private Snapshot(String key, long sequenceNumber, File[] files, InputStream[] ins, long[] lengths) {
|
||||
this.key = key;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.files = files;
|
||||
this.ins = ins;
|
||||
this.lengths = lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for this snapshot's entry, or null if either the
|
||||
* entry has changed since this snapshot was created or if another edit
|
||||
* is in progress.
|
||||
*/
|
||||
public Editor edit() throws IOException {
|
||||
return DiskLruCache.this.edit(key, sequenceNumber);
|
||||
}
|
||||
|
||||
/** Returns file with the value for {@code index}. */
|
||||
public File getFile(int index) {
|
||||
return files[index];
|
||||
}
|
||||
|
||||
/** Returns the unbuffered stream with the value for {@code index}. */
|
||||
public InputStream getInputStream(int index) {
|
||||
return ins[index];
|
||||
}
|
||||
|
||||
/** Returns the string value for {@code index}. */
|
||||
public String getString(int index) throws IOException {
|
||||
return inputStreamToString(getInputStream(index));
|
||||
}
|
||||
|
||||
/** Returns the byte length of the value for {@code index}. */
|
||||
public long getLength(int index) {
|
||||
return lengths[index];
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (InputStream in : ins) {
|
||||
Util.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
// Eat all writes silently. Nom nom.
|
||||
}
|
||||
};
|
||||
|
||||
/** Edits the values for an entry. */
|
||||
public final class Editor {
|
||||
private final Entry entry;
|
||||
private final boolean[] written;
|
||||
private boolean hasErrors;
|
||||
private boolean committed;
|
||||
|
||||
private Editor(Entry entry) {
|
||||
this.entry = entry;
|
||||
this.written = (entry.readable) ? null : new boolean[valueCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unbuffered input stream to read the last committed value,
|
||||
* or null if no value has been committed.
|
||||
*/
|
||||
public InputStream newInputStream(int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new FileInputStream(entry.getCleanFile(index));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last committed value as a string, or null if no value
|
||||
* has been committed.
|
||||
*/
|
||||
public String getString(int index) throws IOException {
|
||||
InputStream in = newInputStream(index);
|
||||
return in != null ? inputStreamToString(in) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new unbuffered output stream to write the value at
|
||||
* {@code index}. If the underlying output stream encounters errors
|
||||
* when writing to the filesystem, this edit will be aborted when
|
||||
* {@link #commit} is called. The returned output stream does not throw
|
||||
* IOExceptions.
|
||||
*/
|
||||
public OutputStream newOutputStream(int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
written[index] = true;
|
||||
}
|
||||
File dirtyFile = entry.getDirtyFile(index);
|
||||
FileOutputStream outputStream;
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Attempt to recreate the cache directory.
|
||||
directory.mkdirs();
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e2) {
|
||||
// We are unable to recover. Silently eat the writes.
|
||||
return NULL_OUTPUT_STREAM;
|
||||
}
|
||||
}
|
||||
return new FaultHidingOutputStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the value at {@code index} to {@code value}. */
|
||||
public void set(int index, String value) throws IOException {
|
||||
Writer writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
|
||||
writer.write(value);
|
||||
} finally {
|
||||
Util.closeQuietly(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits this edit so it is visible to readers. This releases the
|
||||
* edit lock so another edit may be started on the same key.
|
||||
*/
|
||||
public void commit() throws IOException {
|
||||
if (hasErrors) {
|
||||
completeEdit(this, false);
|
||||
remove(entry.key); // The previous entry is stale.
|
||||
} else {
|
||||
completeEdit(this, true);
|
||||
}
|
||||
committed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts this edit. This releases the edit lock so another edit may be
|
||||
* started on the same key.
|
||||
*/
|
||||
public void abort() throws IOException {
|
||||
completeEdit(this, false);
|
||||
}
|
||||
|
||||
public void abortUnlessCommitted() {
|
||||
if (!committed) {
|
||||
try {
|
||||
abort();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FaultHidingOutputStream extends FilterOutputStream {
|
||||
private FaultHidingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override public void write(int oneByte) {
|
||||
try {
|
||||
out.write(oneByte);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void write(byte[] buffer, int offset, int length) {
|
||||
try {
|
||||
out.write(buffer, offset, length);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void flush() {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
private final String key;
|
||||
|
||||
/** Lengths of this entry's files. */
|
||||
private final long[] lengths;
|
||||
|
||||
/** True if this entry has ever been published. */
|
||||
private boolean readable;
|
||||
|
||||
/** The ongoing edit or null if this entry is not being edited. */
|
||||
private Editor currentEditor;
|
||||
|
||||
/** The sequence number of the most recently committed edit to this entry. */
|
||||
private long sequenceNumber;
|
||||
|
||||
private Entry(String key) {
|
||||
this.key = key;
|
||||
this.lengths = new long[valueCount];
|
||||
}
|
||||
|
||||
public String getLengths() throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (long size : lengths) {
|
||||
result.append(' ').append(size);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Set lengths using decimal numbers like "10123". */
|
||||
private void setLengths(String[] strings) throws IOException {
|
||||
if (strings.length != valueCount) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
lengths[i] = Long.parseLong(strings[i]);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
}
|
||||
|
||||
private IOException invalidLengths(String[] strings) throws IOException {
|
||||
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
|
||||
}
|
||||
|
||||
public File getCleanFile(int i) {
|
||||
return new File(directory, key + "" + i);
|
||||
}
|
||||
|
||||
public File getDirtyFile(int i) {
|
||||
return new File(directory, key + "" + i + ".tmp");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/*******************************************************************************
|
||||
* Description: 用于缓存经过高斯模糊的图片
|
||||
*
|
||||
* Author: Freeman
|
||||
*
|
||||
* Date: 2018/9/4
|
||||
*
|
||||
* Copyright: all rights reserved by Freeman.
|
||||
*******************************************************************************/
|
||||
public class DiskLruCacheManager {
|
||||
|
||||
private DiskLruCache diskLruCache;
|
||||
private static DiskLruCacheManager instance;
|
||||
|
||||
private final int MAX_CACHE_SIZE = 64 * 1024 * 1024;
|
||||
|
||||
private DiskLruCacheManager(Context context) {
|
||||
try {
|
||||
diskLruCache = DiskLruCache.open(context.getCacheDir(), 1, 1,
|
||||
MAX_CACHE_SIZE, Integer.MAX_VALUE);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
public static DiskLruCacheManager getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
synchronized (DiskLruCacheManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new DiskLruCacheManager(context.getApplicationContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void put(String url, Bitmap bitmap) {
|
||||
if (TextUtils.isEmpty(url) || bitmap == null || bitmap.isRecycled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DiskLruCache.Editor editor = diskLruCache.edit(getKey(url));
|
||||
OutputStream outputStream = editor.newOutputStream(0);
|
||||
if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) {
|
||||
editor.commit();
|
||||
}
|
||||
diskLruCache.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap get(String url) {
|
||||
try {
|
||||
DiskLruCache.Snapshot snapshot = diskLruCache.get(getKey(url));
|
||||
if (snapshot != null) {
|
||||
InputStream inputStream = snapshot.getInputStream(0);
|
||||
return BitmapFactory.decodeStream(inputStream);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getKey(String url) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
byte[] md5 = digest.digest(url.getBytes());
|
||||
BigInteger bigInteger = new BigInteger(1, md5);
|
||||
return bigInteger.toString(16);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
diskLruCache.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/**
|
||||
* Created by jay on 11/7/15.
|
||||
*/
|
||||
public class FastBlurUtil {
|
||||
|
||||
public static Bitmap doBlur(Bitmap sentBitmap, int scaleRadius, int radius) {
|
||||
|
||||
// Stack Blur v1.0 from
|
||||
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
|
||||
//
|
||||
// Java Author: Mario Klingemann <mario at quasimondo.com>
|
||||
// http://incubator.quasimondo.com
|
||||
// created Feburary 29, 2004
|
||||
// Android port : Yahel Bouaziz <yahel at kayenko.com>
|
||||
// http://www.kayenko.com
|
||||
// ported april 5th, 2012
|
||||
|
||||
// This is a compromise between Gaussian Blur and Box blur
|
||||
// It creates much better looking blurs than Box Blur, but is
|
||||
// 7x faster than my Gaussian Blur implementation.
|
||||
//
|
||||
// I called it Stack Blur because this describes best how this
|
||||
// filter works internally: it creates a kind of moving stack
|
||||
// of colors whilst scanning through the image. Thereby it
|
||||
// just has to add one new block of color to the right side
|
||||
// of the stack and remove the leftmost color. The remaining
|
||||
// colors on the topmost layer of the stack are either added on
|
||||
// or reduced by one, depending on if they are on the right or
|
||||
// on the left side of the stack.
|
||||
//
|
||||
// If you are using this algorithm in your code please add
|
||||
// the following line:
|
||||
//
|
||||
// Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
|
||||
if (scaleRadius > 0) {
|
||||
sentBitmap = Bitmap.createScaledBitmap(sentBitmap, sentBitmap.getWidth() / scaleRadius,
|
||||
sentBitmap.getHeight() / scaleRadius, false);
|
||||
}
|
||||
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
|
||||
|
||||
if (radius < 1) {
|
||||
return (null);
|
||||
}
|
||||
int w = bitmap.getWidth();
|
||||
int h = bitmap.getHeight();
|
||||
|
||||
int[] pix = new int[w * h];
|
||||
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
|
||||
|
||||
int wm = w - 1;
|
||||
int hm = h - 1;
|
||||
int wh = w * h;
|
||||
int div = radius + radius + 1;
|
||||
|
||||
int r[] = new int[wh];
|
||||
int g[] = new int[wh];
|
||||
int b[] = new int[wh];
|
||||
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
|
||||
int vmin[] = new int[Math.max(w, h)];
|
||||
|
||||
int divsum = (div + 1) >> 1;
|
||||
divsum *= divsum;
|
||||
int dv[] = new int[256 * divsum];
|
||||
for (i = 0; i < 256 * divsum; i++) {
|
||||
dv[i] = (i / divsum);
|
||||
}
|
||||
|
||||
yw = yi = 0;
|
||||
|
||||
int[][] stack = new int[div][3];
|
||||
int stackpointer;
|
||||
int stackstart;
|
||||
int[] sir;
|
||||
int rbs;
|
||||
int r1 = radius + 1;
|
||||
int routsum, goutsum, boutsum;
|
||||
int rinsum, ginsum, binsum;
|
||||
|
||||
for (y = 0; y < h; y++) {
|
||||
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
|
||||
for (i = -radius; i <= radius; i++) {
|
||||
p = pix[yi + Math.min(wm, Math.max(i, 0))];
|
||||
sir = stack[i + radius];
|
||||
sir[0] = (p & 0xff0000) >> 16;
|
||||
sir[1] = (p & 0x00ff00) >> 8;
|
||||
sir[2] = (p & 0x0000ff);
|
||||
rbs = r1 - Math.abs(i);
|
||||
rsum += sir[0] * rbs;
|
||||
gsum += sir[1] * rbs;
|
||||
bsum += sir[2] * rbs;
|
||||
if (i > 0) {
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
} else {
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
}
|
||||
}
|
||||
stackpointer = radius;
|
||||
|
||||
for (x = 0; x < w; x++) {
|
||||
|
||||
r[yi] = dv[rsum];
|
||||
g[yi] = dv[gsum];
|
||||
b[yi] = dv[bsum];
|
||||
|
||||
rsum -= routsum;
|
||||
gsum -= goutsum;
|
||||
bsum -= boutsum;
|
||||
|
||||
stackstart = stackpointer - radius + div;
|
||||
sir = stack[stackstart % div];
|
||||
|
||||
routsum -= sir[0];
|
||||
goutsum -= sir[1];
|
||||
boutsum -= sir[2];
|
||||
|
||||
if (y == 0) {
|
||||
vmin[x] = Math.min(x + radius + 1, wm);
|
||||
}
|
||||
p = pix[yw + vmin[x]];
|
||||
|
||||
sir[0] = (p & 0xff0000) >> 16;
|
||||
sir[1] = (p & 0x00ff00) >> 8;
|
||||
sir[2] = (p & 0x0000ff);
|
||||
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
|
||||
rsum += rinsum;
|
||||
gsum += ginsum;
|
||||
bsum += binsum;
|
||||
|
||||
stackpointer = (stackpointer + 1) % div;
|
||||
sir = stack[(stackpointer) % div];
|
||||
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
|
||||
rinsum -= sir[0];
|
||||
ginsum -= sir[1];
|
||||
binsum -= sir[2];
|
||||
|
||||
yi++;
|
||||
}
|
||||
yw += w;
|
||||
}
|
||||
for (x = 0; x < w; x++) {
|
||||
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
|
||||
yp = -radius * w;
|
||||
for (i = -radius; i <= radius; i++) {
|
||||
yi = Math.max(0, yp) + x;
|
||||
|
||||
sir = stack[i + radius];
|
||||
|
||||
sir[0] = r[yi];
|
||||
sir[1] = g[yi];
|
||||
sir[2] = b[yi];
|
||||
|
||||
rbs = r1 - Math.abs(i);
|
||||
|
||||
rsum += r[yi] * rbs;
|
||||
gsum += g[yi] * rbs;
|
||||
bsum += b[yi] * rbs;
|
||||
|
||||
if (i > 0) {
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
} else {
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
}
|
||||
|
||||
if (i < hm) {
|
||||
yp += w;
|
||||
}
|
||||
}
|
||||
yi = x;
|
||||
stackpointer = radius;
|
||||
for (y = 0; y < h; y++) {
|
||||
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
|
||||
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
|
||||
|
||||
rsum -= routsum;
|
||||
gsum -= goutsum;
|
||||
bsum -= boutsum;
|
||||
|
||||
stackstart = stackpointer - radius + div;
|
||||
sir = stack[stackstart % div];
|
||||
|
||||
routsum -= sir[0];
|
||||
goutsum -= sir[1];
|
||||
boutsum -= sir[2];
|
||||
|
||||
if (x == 0) {
|
||||
vmin[y] = Math.min(y + r1, hm) * w;
|
||||
}
|
||||
p = x + vmin[y];
|
||||
|
||||
sir[0] = r[p];
|
||||
sir[1] = g[p];
|
||||
sir[2] = b[p];
|
||||
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
|
||||
rsum += rinsum;
|
||||
gsum += ginsum;
|
||||
bsum += binsum;
|
||||
|
||||
stackpointer = (stackpointer + 1) % div;
|
||||
sir = stack[stackpointer];
|
||||
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
|
||||
rinsum -= sir[0];
|
||||
ginsum -= sir[1];
|
||||
binsum -= sir[2];
|
||||
|
||||
yi += w;
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
|
||||
|
||||
return (bitmap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class GlideBlurTransform extends BitmapTransformation {
|
||||
|
||||
private String key;
|
||||
private Context context;
|
||||
private int blurRadius;
|
||||
|
||||
public GlideBlurTransform(Context context, String key, int blurRadius ) {
|
||||
this.context = context;
|
||||
this.key = key;
|
||||
this.blurRadius = blurRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap transform( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight ) {
|
||||
Bitmap bitmap = FastBlurUtil.doBlur( toTransform, 1, blurRadius );
|
||||
// 缓存高斯模糊图片
|
||||
DiskLruCacheManager.getInstance( context ).put( key, bitmap );
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey( MessageDigest messageDigest ) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Buffers input from an {@link InputStream} for reading lines.
|
||||
*
|
||||
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
|
||||
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
|
||||
* line at end of input is invalid and will be ignored, the caller may use {@code
|
||||
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
|
||||
*
|
||||
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
|
||||
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
|
||||
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
|
||||
* end-of-input reporting and a more restrictive definition of a line.
|
||||
*
|
||||
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
|
||||
* and 10, respectively, and the representation of no other character contains these values.
|
||||
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
|
||||
* The default charset is US_ASCII.
|
||||
*/
|
||||
class StrictLineReader implements Closeable {
|
||||
private static final byte CR = (byte) '\r';
|
||||
private static final byte LF = (byte) '\n';
|
||||
|
||||
private final InputStream in;
|
||||
private final Charset charset;
|
||||
|
||||
/*
|
||||
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
|
||||
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
|
||||
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
|
||||
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
|
||||
*/
|
||||
private byte[] buf;
|
||||
private int pos;
|
||||
private int end;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
|
||||
*
|
||||
* @param in the {@code InputStream} to read data from.
|
||||
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
|
||||
* supported.
|
||||
* @throws NullPointerException if {@code in} or {@code charset} is null.
|
||||
* @throws IllegalArgumentException if the specified charset is not supported.
|
||||
*/
|
||||
public StrictLineReader(InputStream in, Charset charset) {
|
||||
this(in, 8192, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LineReader} with the specified capacity and charset.
|
||||
*
|
||||
* @param in the {@code InputStream} to read data from.
|
||||
* @param capacity the capacity of the buffer.
|
||||
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
|
||||
* supported.
|
||||
* @throws NullPointerException if {@code in} or {@code charset} is null.
|
||||
* @throws IllegalArgumentException if {@code capacity} is negative or zero
|
||||
* or the specified charset is not supported.
|
||||
*/
|
||||
public StrictLineReader(InputStream in, int capacity, Charset charset) {
|
||||
if (in == null || charset == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (capacity < 0) {
|
||||
throw new IllegalArgumentException("capacity <= 0");
|
||||
}
|
||||
if (!(charset.equals(Util.US_ASCII))) {
|
||||
throw new IllegalArgumentException("Unsupported encoding");
|
||||
}
|
||||
|
||||
this.in = in;
|
||||
this.charset = charset;
|
||||
buf = new byte[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the reader by closing the underlying {@code InputStream} and
|
||||
* marking this reader as closed.
|
||||
*
|
||||
* @throws IOException for errors when closing the underlying {@code InputStream}.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
synchronized (in) {
|
||||
if (buf != null) {
|
||||
buf = null;
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
|
||||
* this end of line marker is not included in the result.
|
||||
*
|
||||
* @return the next line from the input.
|
||||
* @throws IOException for underlying {@code InputStream} errors.
|
||||
* @throws EOFException for the end of source stream.
|
||||
*/
|
||||
public String readLine() throws IOException {
|
||||
synchronized (in) {
|
||||
if (buf == null) {
|
||||
throw new IOException("LineReader is closed");
|
||||
}
|
||||
|
||||
// Read more data if we are at the end of the buffered data.
|
||||
// Though it's an error to read after an exception, we will let {@code fillBuf()}
|
||||
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
|
||||
if (pos >= end) {
|
||||
fillBuf();
|
||||
}
|
||||
// Try to find LF in the buffered data and return the line if successful.
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
|
||||
String res = new String(buf, pos, lineEnd - pos, charset.name());
|
||||
pos = i + 1;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's anticipate up to 80 characters on top of those already read.
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
|
||||
@Override
|
||||
public String toString() {
|
||||
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
||||
try {
|
||||
return new String(buf, 0, length, charset.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // Since we control the charset this will never happen.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
out.write(buf, pos, end - pos);
|
||||
// Mark unterminated line in case fillBuf throws EOFException or IOException.
|
||||
end = -1;
|
||||
fillBuf();
|
||||
// Try to find LF in the buffered data and return the line if successful.
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
if (i != pos) {
|
||||
out.write(buf, pos, i - pos);
|
||||
}
|
||||
pos = i + 1;
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads new input data into the buffer. Call only with pos == end or end == -1,
|
||||
* depending on the desired outcome if the function throws.
|
||||
*/
|
||||
private void fillBuf() throws IOException {
|
||||
int result = in.read(buf, 0, buf.length);
|
||||
if (result == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
pos = 0;
|
||||
end = result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/** Junk drawer of utility methods. */
|
||||
final class Util {
|
||||
static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||
static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private Util() {
|
||||
}
|
||||
|
||||
static String readFully(Reader reader) throws IOException {
|
||||
try {
|
||||
StringWriter writer = new StringWriter();
|
||||
char[] buffer = new char[1024];
|
||||
int count;
|
||||
while ((count = reader.read(buffer)) != -1) {
|
||||
writer.write(buffer, 0, count);
|
||||
}
|
||||
return writer.toString();
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the contents of {@code dir}. Throws an IOException if any file
|
||||
* could not be deleted, or if {@code dir} is not a readable directory.
|
||||
*/
|
||||
static void deleteContents(File dir) throws IOException {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
throw new IOException("not a readable directory: " + dir);
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("failed to delete file: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void closeQuietly(/*Auto*/Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
0
OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/windowdispatch/ReflectionUtils.java
Executable file → Normal file
0
OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/windowdispatch/ReflectionUtils.java
Executable file → Normal file
@@ -1,15 +1,18 @@
|
||||
package com.mogo.och.taxi.passenger.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
|
||||
import com.mogo.eagle.core.utilcode.util.TimeTransformUtils
|
||||
import com.mogo.eagle.core.widget.media.video.TextureVideoViewOutlineProvider
|
||||
import com.mogo.och.taxi.passenger.R
|
||||
import com.mogo.och.taxi.passenger.model.TaxiPassengerModel
|
||||
import com.shuyu.gsyvideoplayer.GSYVideoManager
|
||||
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
|
||||
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
|
||||
@@ -24,21 +27,11 @@ import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge
|
||||
*/
|
||||
class ConsultVideoPlayer : StandardGSYVideoPlayer {
|
||||
|
||||
companion object {
|
||||
const val PLAY_EVT_PLAY_LOADING = 1000
|
||||
const val PLAY_EVT_PLAY_BEGIN = 2000
|
||||
const val PLAY_EVT_PLAY_ERROR = 3000
|
||||
}
|
||||
|
||||
private var playListener: PlayListener? = null
|
||||
private lateinit var start: ImageView
|
||||
private lateinit var coverImage: ImageView
|
||||
lateinit var coverImage: ImageView
|
||||
private lateinit var currentTimeTextView: TextView
|
||||
private lateinit var totalTimeTextView: TextView
|
||||
|
||||
interface PlayListener {
|
||||
fun onPlayEvent(event: Int)
|
||||
}
|
||||
private lateinit var layoutBottom: ConstraintLayout
|
||||
|
||||
constructor(context: Context?) : super(context)
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||
@@ -50,7 +43,7 @@ class ConsultVideoPlayer : StandardGSYVideoPlayer {
|
||||
coverImage = findViewById(R.id.thumbImage)
|
||||
currentTimeTextView = findViewById(R.id.current)
|
||||
totalTimeTextView = findViewById(R.id.total)
|
||||
|
||||
layoutBottom = findViewById(R.id.layout_bottom)
|
||||
if (mThumbImageViewLayout != null
|
||||
&& (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)
|
||||
) {
|
||||
@@ -86,9 +79,6 @@ class ConsultVideoPlayer : StandardGSYVideoPlayer {
|
||||
forceChange: Boolean
|
||||
) {
|
||||
super.setProgressAndTime(progress, secProgress, currentTime, totalTime, forceChange)
|
||||
mBottomContainer?.visibility = View.VISIBLE
|
||||
mProgressBar?.visibility = View.VISIBLE
|
||||
start.visibility = View.VISIBLE
|
||||
//时间显示
|
||||
currentTimeTextView.text = TimeTransformUtils.stringForTime(currentTime)
|
||||
totalTimeTextView.text = TimeTransformUtils.stringForTime(totalTime)
|
||||
@@ -98,49 +88,6 @@ class ConsultVideoPlayer : StandardGSYVideoPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
fun setPlayListener(listener: PlayListener) {
|
||||
this.playListener = listener
|
||||
}
|
||||
|
||||
override fun changeUiToCompleteShow() {
|
||||
super.changeUiToCompleteShow()
|
||||
}
|
||||
|
||||
override fun hideAllWidget() {
|
||||
super.hideAllWidget()
|
||||
mBottomContainer?.visibility = View.VISIBLE
|
||||
mProgressBar?.visibility = View.VISIBLE
|
||||
start?.visibility = View.VISIBLE
|
||||
start.setImageResource(R.drawable.notice_video_pause)
|
||||
}
|
||||
|
||||
override fun changeUiToPrepareingClear() {
|
||||
super.changeUiToPrepareingClear()
|
||||
mBottomContainer?.visibility = View.INVISIBLE
|
||||
mProgressBar?.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun changeUiToPlayingBufferingClear() {
|
||||
super.changeUiToPlayingBufferingClear()
|
||||
mBottomContainer?.visibility = View.INVISIBLE
|
||||
mProgressBar?.visibility = View.GONE
|
||||
|
||||
}
|
||||
|
||||
// override fun changeUiToClear() {
|
||||
// super.changeUiToClear()
|
||||
// }
|
||||
|
||||
override fun changeUiToCompleteClear() {
|
||||
super.changeUiToCompleteClear()
|
||||
mBottomContainer?.visibility = View.INVISIBLE
|
||||
mProgressBar?.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onAutoCompletion() {
|
||||
super.onAutoCompletion()
|
||||
}
|
||||
|
||||
override fun showWifiDialog() {
|
||||
//直接播放,不显示WIFI对话框
|
||||
startPlayLogic()
|
||||
@@ -158,12 +105,7 @@ class ConsultVideoPlayer : StandardGSYVideoPlayer {
|
||||
}
|
||||
|
||||
override fun onCompletion() {
|
||||
mBottomContainer?.visibility = View.VISIBLE
|
||||
mProgressBar?.visibility = View.VISIBLE
|
||||
start.visibility = View.VISIBLE
|
||||
start.setImageResource(R.drawable.notice_video_after_pause)
|
||||
|
||||
isPostBufferUpdate = false
|
||||
}
|
||||
|
||||
override fun onSurfaceUpdated(surface: Surface) {
|
||||
@@ -175,24 +117,15 @@ class ConsultVideoPlayer : StandardGSYVideoPlayer {
|
||||
|
||||
override fun onPrepared() {
|
||||
super.onPrepared()
|
||||
|
||||
playListener?.onPlayEvent(PLAY_EVT_PLAY_LOADING)
|
||||
}
|
||||
|
||||
private var isPostBufferUpdate = false
|
||||
|
||||
override fun onBufferingUpdate(percent: Int) {
|
||||
super.onBufferingUpdate(percent)
|
||||
if (!isPostBufferUpdate && percent == 0) {
|
||||
isPostBufferUpdate = true
|
||||
playListener?.onPlayEvent(PLAY_EVT_PLAY_BEGIN)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onError(what: Int, extra: Int) {
|
||||
super.onError(what, extra)
|
||||
playListener?.onPlayEvent(PLAY_EVT_PLAY_ERROR)
|
||||
isPostBufferUpdate = false
|
||||
}
|
||||
|
||||
override fun setViewShowState(view: View?, visibility: Int) {
|
||||
@@ -204,11 +137,10 @@ class ConsultVideoPlayer : StandardGSYVideoPlayer {
|
||||
|
||||
override fun onSurfaceAvailable(surface: Surface) {
|
||||
super.onSurfaceAvailable(surface)
|
||||
mProgressBar?.visibility = View.GONE
|
||||
if (GSYVideoType.getRenderType() != GSYVideoType.TEXTURE) {
|
||||
if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
|
||||
mThumbImageViewLayout.visibility = View.INVISIBLE
|
||||
}
|
||||
// if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
|
||||
// mThumbImageViewLayout.visibility = View.INVISIBLE
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 191 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 191 KiB |
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer
|
||||
android:id="@+id/video_item_player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</FrameLayout>
|
||||
<com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/video_item_player"
|
||||
android:layout_width="1734px"
|
||||
android:layout_height="973px" />
|
||||
|
||||
@@ -32,10 +32,12 @@
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_video_playlist"
|
||||
android:layout_width="1734px"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="973px"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="@dimen/dp_130"
|
||||
android:layout_marginStart="156px"
|
||||
android:layout_marginEnd="156px"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_mogo_consult"/>
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
android:id="@+id/thumbImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:scaleType="fitXY" />
|
||||
android:layout_centerInParent="true" />
|
||||
</RelativeLayout>
|
||||
|
||||
<!--局部播放器-->
|
||||
|
||||
Reference in New Issue
Block a user