diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/bean/TaxiPassengerVideoPlay.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/bean/TaxiPassengerVideoPlay.java new file mode 100644 index 0000000000..204760da46 --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/bean/TaxiPassengerVideoPlay.java @@ -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; + } +} diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/TaxiPassengerBaseFragment.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/TaxiPassengerBaseFragment.java index d13c32ec71..d758efb3bf 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/TaxiPassengerBaseFragment.java +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/TaxiPassengerBaseFragment.java @@ -121,16 +121,16 @@ public class TaxiPassengerBaseFragment extends MvpFragment { - //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())); }); } diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ItemViewTouchListener.kt b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ItemViewTouchListener.kt index 9ae678ff78..bf061c2902 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ItemViewTouchListener.kt +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ItemViewTouchListener.kt @@ -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 diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ListAdapter.kt b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ListAdapter.kt index 0b77d23ef7..d27143ba02 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ListAdapter.kt +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/ListAdapter.kt @@ -31,6 +31,11 @@ class ListAdapter(private val context: Context,val list: MutableList? = null + private var taxiPassengerMogoMoviesView: WeakReference? = 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(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(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(R.id.lv_select_item) + val integers = mutableListOf() + + 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(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(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(R.id.lv_select_item) - val integers = mutableListOf() - 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) } } \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/model/LeftMenuModel.kt b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/model/LeftMenuModel.kt index 961788a959..6907442c5d 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/model/LeftMenuModel.kt +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/leftmenu/model/LeftMenuModel.kt @@ -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 ) \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerItemVideoHolder.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerItemVideoHolder.java index 17c96f4e08..1db9dca924 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerItemVideoHolder.java +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerItemVideoHolder.java @@ -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(); - - } - } diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerVideoAdapter.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerVideoAdapter.java index ad0ae64c7f..d6a6d7cfc8 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerVideoAdapter.java +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/RecyclerVideoAdapter.java @@ -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 { private final static String TAG = "RecyclerBaseAdapter"; - private List itemDataList = null; + private List itemDataList = null; private Context context = null; - public RecyclerVideoAdapter(Context context, List itemDataList) { + public RecyclerVideoAdapter(Context context, List 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 data) { - itemDataList = data; - notifyDataSetChanged(); - } + } diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/TaxiPassengerMogoConsultView.kt b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/TaxiPassengerMogoConsultView.kt index 322f82a2a5..0a11763663 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/TaxiPassengerMogoConsultView.kt +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/TaxiPassengerMogoConsultView.kt @@ -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() - arrayListOf.add("") - arrayListOf.add("") + val arrayListOf = ArrayList() + 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() { + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + 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 { diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/TaxiPassengerMogoMoviesView.kt b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/TaxiPassengerMogoMoviesView.kt new file mode 100644 index 0000000000..56a6cc186c --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/TaxiPassengerMogoMoviesView.kt @@ -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() + 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() { + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + 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() + } + } + +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselChildSelectionListener.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselChildSelectionListener.java new file mode 100644 index 0000000000..5bd1fa4860 --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselChildSelectionListener.java @@ -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); +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselLayoutManager.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselLayoutManager.java new file mode 100644 index 0000000000..f6ae3480ec --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselLayoutManager.java @@ -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}.
+ *
+ * This LayoutManager supports only fixedSized adapter items.
+ *
+ * This LayoutManager supports {@link CarouselLayoutManager#HORIZONTAL} and {@link CarouselLayoutManager#VERTICAL} orientations.
+ *
+ * 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.
+ *
+ * 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}
+ *
+ */ +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 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.
+ *
+ * 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.
+ *
+ * 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 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.
+ *
+ * Returns {@link #convertItemPositionDiffToSmoothPositionDiff(float)} * (size off area above center item when it is on the center).
+ * Sign is: plus if this item is bellow center line, minus if not
+ *
+ * ----- - area above it
+ * ||||| - center item
+ * ----- - area bellow it (it has the same size as are above center item)
+ * + * @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.
+ * 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.
+ * 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.
+ *
+ * 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 every 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.
+ *
+ * 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> 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> iterator = mReusedItems.iterator(); + while (iterator.hasNext()) { + final WeakReference 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 CREATOR + = new Creator() { + @Override + public CarouselSavedState createFromParcel(final Parcel parcel) { + return new CarouselSavedState(parcel); + } + + @Override + public CarouselSavedState[] newArray(final int i) { + return new CarouselSavedState[i]; + } + }; + } +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselZoomPostLayoutListener.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselZoomPostLayoutListener.java new file mode 100644 index 0000000000..c91711e1c9 --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CarouselZoomPostLayoutListener.java @@ -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.
+ * We are trying to make items scaling quicker for closer items for center and slower for when they are far away.
+ * 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); + } +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CenterScrollListener.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CenterScrollListener.java new file mode 100644 index 0000000000..4b04e7b957 --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/CenterScrollListener.java @@ -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.
+ * 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) { + + } +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/DefaultChildSelectionListener.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/DefaultChildSelectionListener.java new file mode 100644 index 0000000000..6e4d2d8e8c --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/DefaultChildSelectionListener.java @@ -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); + } +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/ItemTransformation.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/ItemTransformation.java new file mode 100644 index 0000000000..14003ca28b --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/video/layoutmanage/ItemTransformation.java @@ -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; + } +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/DiskLruCache.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/DiskLruCache.java new file mode 100644 index 0000000000..d20bd4e65e --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/DiskLruCache.java @@ -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 [a-z0-9_-]{1,64}. Values are byte sequences, + * accessible as streams or files. Each value must be between {@code 0} and + * {@code Integer.MAX_VALUE} bytes in length. + * + *

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. + * + *

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. + * + *

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. + *

    + *
  • When an entry is being created it is necessary to + * supply a full set of values; the empty value should be used as a + * placeholder if necessary. + *
  • When an entry is being edited, it is not necessary + * to supply data for every value; values default to their previous + * value. + *
+ * 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. + * + *

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. + * + *

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 lruEntries = + new LinkedHashMap(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()); + private final Callable cleanupCallable = new Callable() { + 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 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(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 toEvict = lruEntries.entrySet().iterator().next(); + remove(toEvict.getKey()); + } + } + + private void trimToFileCount() throws IOException { + while (fileCount > maxFileCount) { + Map.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"); + } + } +} diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/DiskLruCacheManager.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/DiskLruCacheManager.java new file mode 100644 index 0000000000..424c5dce56 --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/DiskLruCacheManager.java @@ -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); + } + } +} diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/FastBlurUtil.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/FastBlurUtil.java new file mode 100644 index 0000000000..bab7819ba1 --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/FastBlurUtil.java @@ -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 + // http://incubator.quasimondo.com + // created Feburary 29, 2004 + // Android port : Yahel Bouaziz + // 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 + 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); + } +} diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/GlideBlurTransform.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/GlideBlurTransform.java new file mode 100644 index 0000000000..eee5c0cfdc --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/GlideBlurTransform.java @@ -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 ) { + + } +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/StrictLineReader.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/StrictLineReader.java new file mode 100644 index 0000000000..94f756e38b --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/StrictLineReader.java @@ -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. + * + *

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}. + * + *

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. + * + *

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; + } +} + diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/Util.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/Util.java new file mode 100644 index 0000000000..7d9ad39ccb --- /dev/null +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/blur/Util.java @@ -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) { + } + } + } +} \ No newline at end of file diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/windowdispatch/ReflectionUtils.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/utils/windowdispatch/ReflectionUtils.java old mode 100755 new mode 100644 diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/widget/ConsultVideoPlayer.kt b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/widget/ConsultVideoPlayer.kt index da85038f80..28ae85501b 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/widget/ConsultVideoPlayer.kt +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/widget/ConsultVideoPlayer.kt @@ -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 +// } } } diff --git a/OCH/mogo-och-taxi-passenger/src/main/res/drawable-xhdpi-2560x1440/taxi_p_mogo_live_select.png b/OCH/mogo-och-taxi-passenger/src/main/res/drawable-xhdpi-2560x1440/taxi_p_mogo_live_select.png new file mode 100644 index 0000000000..b0436faf24 Binary files /dev/null and b/OCH/mogo-och-taxi-passenger/src/main/res/drawable-xhdpi-2560x1440/taxi_p_mogo_live_select.png differ diff --git a/OCH/mogo-och-taxi-passenger/src/main/res/drawable-xhdpi/taxi_p_mogo_live_select.png b/OCH/mogo-och-taxi-passenger/src/main/res/drawable-xhdpi/taxi_p_mogo_live_select.png new file mode 100644 index 0000000000..b0436faf24 Binary files /dev/null and b/OCH/mogo-och-taxi-passenger/src/main/res/drawable-xhdpi/taxi_p_mogo_live_select.png differ diff --git a/OCH/mogo-och-taxi-passenger/src/main/res/layout/list_video_item_normal.xml b/OCH/mogo-och-taxi-passenger/src/main/res/layout/list_video_item_normal.xml index 64a33f5bcb..0d891e27eb 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/res/layout/list_video_item_normal.xml +++ b/OCH/mogo-och-taxi-passenger/src/main/res/layout/list_video_item_normal.xml @@ -1,11 +1,5 @@ - - - - - + diff --git a/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_arrived_mogo_consult.xml b/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_arrived_mogo_consult.xml index b06e2ec461..11d9264e47 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_arrived_mogo_consult.xml +++ b/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_arrived_mogo_consult.xml @@ -32,10 +32,12 @@ diff --git a/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_video_show.xml b/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_video_show.xml index 02629c9710..c160faf6c8 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_video_show.xml +++ b/OCH/mogo-och-taxi-passenger/src/main/res/layout/taxi_p_video_show.xml @@ -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" />