Merge remote-tracking branch 'origin/dev_robotaxi-d-app-module_280_220608_2.8.0' into dev_robotaxi-d-app-module_280_220608_2.8.0

This commit is contained in:
wangmingjun
2022-06-24 14:21:43 +08:00
108 changed files with 6387 additions and 96 deletions

View File

@@ -49,6 +49,7 @@ dependencies {
implementation rootProject.ext.dependencies.arouter
implementation rootProject.ext.dependencies.androidxrecyclerview
implementation rootProject.ext.dependencies.material
implementation rootProject.ext.dependencies.flexbox
annotationProcessor rootProject.ext.dependencies.aroutercompiler
implementation rootProject.ext.dependencies.rxandroid
implementation rootProject.ext.dependencies.androidxconstraintlayout

View File

@@ -0,0 +1,26 @@
package com.mogo.och.taxi.passenger.bean;
import com.mogo.eagle.core.data.BaseData;
import java.util.List;
/**
* Created by pangfan on 2021/8/19
*
* 查询订单返回数据结构
*/
public class TaxiPassengerAllStarWorld extends BaseData {
public List<TaxiPassengerStarWorld> data;
public static class TaxiPassengerStarWorld {
public TaxiPassengerStarWorld(String labelInfo) {
this.labelInfo = labelInfo;
}
public String labelNo;
public String labelInfo;
public String star;
public String sort;
public Boolean isSelect = false;
}
}

View File

@@ -1,5 +1,7 @@
package com.mogo.och.taxi.passenger.bean;
import java.util.List;
/**
* Created by pangfan on 2021/8/19
*
@@ -9,9 +11,17 @@ public class TaxiPassengerScoreUpdateOrderReqBean {
public String orderNo;
public int star;
public List<TaxiPassengerAllStarWorld.TaxiPassengerStarWorld> evalLabeBasicList;
public TaxiPassengerScoreUpdateOrderReqBean(String orderNo, int star) {
public TaxiPassengerScoreUpdateOrderReqBean(String orderNo, int star,List<TaxiPassengerAllStarWorld.TaxiPassengerStarWorld> data) {
this.orderNo = orderNo;
this.star = star;
for (TaxiPassengerAllStarWorld.TaxiPassengerStarWorld datum : data) {
datum.isSelect=null;
datum.sort = null;
datum.star = null;
}
this.evalLabeBasicList = data;
}
}

View File

@@ -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;
}
}

View File

@@ -1,5 +1,7 @@
package com.mogo.och.taxi.passenger.callback;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerScoreUpdateOrderReqBean;
public interface ITaxiPassengerScoreCallback {
void onScoreCallback(int fraction,String orderNo);
void onScoreCallback(TaxiPassengerScoreUpdateOrderReqBean taxiPassengerScoreUpdateOrderReqBean);
}

View File

@@ -40,6 +40,7 @@ import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrdersInServiceQueryRespBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerQueryOrderRouteResp;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerStartReqBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerScoreUpdateOrderReqBean;
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerADASStatusCallback;
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerAutopilotPlanningCallback;
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerControllerStatusCallback;
@@ -731,7 +732,6 @@ public class TaxiPassengerModel implements IOCHTaxiPassengerNaviChangedCallback
public void checkPhoneAndUpdateStatus(String phoneTail,ITaxiPassengerCommonCallback commonCallback) {
if (mCurrentOCHOrder == null) return;
CallerLogger.INSTANCE.d(M_TAXI_P + TAG, "--route--- checkPhoneAndUpdateStatus");
TaxiPassengerServiceManager.getInstance().checkPhoneAndUpdateOrderStatus(mContext, mCurrentOCHOrder.orderNo,
phoneTail, new TaxiPassengerServiceCallback<TaxiPassengerBaseRespBean>() {
@Override
@@ -749,14 +749,14 @@ public class TaxiPassengerModel implements IOCHTaxiPassengerNaviChangedCallback
@Override
public void onFail(int code, String msg) {
ToastUtils.showLong("当前网络异常,请重新验证;若始终异常,请您在手机端取消行程,给您带来不便,十分抱歉!");
CallerLogger.INSTANCE.e(TAG,"提交用户输入的手机后4位、并进行状态扭转 后台结果错误"+code+msg);
CallerLogger.INSTANCE.e(M_TAXI_P + TAG,"提交用户输入的手机后4位、并进行状态扭转 后台结果错误"+code+msg);
}
});
}
public void arrivedAndScore(int score,String orderNo ,ITaxiPassengerCommonValueCallback<Boolean> commonCallback) {
if (orderNo == null) return;
TaxiPassengerServiceManager.getInstance().arrivedAndScore(mContext,orderNo,score,
public void arrivedAndScore(TaxiPassengerScoreUpdateOrderReqBean taxiPassengerScoreUpdateOrderReqBean , ITaxiPassengerCommonValueCallback<Boolean> commonCallback) {
if (taxiPassengerScoreUpdateOrderReqBean.orderNo == null) return;
TaxiPassengerServiceManager.getInstance().arrivedAndScore(mContext,taxiPassengerScoreUpdateOrderReqBean,
new TaxiPassengerServiceCallback<TaxiPassengerBaseRespBean>() {
@Override
public void onSuccess(TaxiPassengerBaseRespBean data) {
@@ -768,7 +768,7 @@ public class TaxiPassengerModel implements IOCHTaxiPassengerNaviChangedCallback
@Override
public void onError() {
ToastUtils.showLong("网络错误请稍后再试");
CallerLogger.INSTANCE.e(TAG,"对订单进行打分 1-5分 网络错误");
CallerLogger.INSTANCE.e(M_TAXI_P + TAG,"对订单进行打分 1-5分 网络错误");
if(commonCallback!=null) {
commonCallback.onCommonCallback(false);
}
@@ -776,7 +776,7 @@ public class TaxiPassengerModel implements IOCHTaxiPassengerNaviChangedCallback
@Override
public void onFail(int code, String msg) {
CallerLogger.INSTANCE.e(TAG,"对订单进行打分 1-5分 后台结果错误"+code+msg);
CallerLogger.INSTANCE.e(M_TAXI_P + TAG,"对订单进行打分 1-5分 后台结果错误"+code+msg);
if(commonCallback!=null) {
commonCallback.onCommonCallback(false);
}

View File

@@ -1,4 +1,5 @@
package com.mogo.och.taxi.passenger.network;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerAllStarWorld;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerBaseRespBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerCheckPhoneUpdateOrderReqBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRemainingResp;
@@ -74,18 +75,33 @@ interface TaxiPassengerServiceApi {
* @return
*/
@Headers( {"Content-type:application/json;charset=UTF-8"} )
@POST( "/autopilot-car-hailing/order/v2/vehicle/taxi/passenger/verification/phone" )
@POST( "/autopilot-car-hailing/cab/flow/v1/driver/taxi/passenger/verification/phone" )
Observable<TaxiPassengerBaseRespBean> checkPhoneAndUpdateOrderStatus(@Header ("appId") String appId, @Header("ticket") String ticket, @Body TaxiPassengerCheckPhoneUpdateOrderReqBean data);
/**
* 对订单进行打分 1-5分
* 对订单进行打分 1-5分 加上文案评论
* @param data
* @return
*/
@Headers( {"Content-type:application/json;charset=UTF-8"} )
@POST( "/autopilot-car-hailing/evaluation/vehicle/taxi/passenger/add" )
@POST( "/autopilot-car-hailing/evaluation/info/driver/taxi/submit" )
Observable<TaxiPassengerBaseRespBean> arrivedAndScore(@Header ("appId") String appId, @Header("ticket") String ticket, @Body TaxiPassengerScoreUpdateOrderReqBean data);
/**
* 获取星星对应的文案(所有文案)
* @return
*/
@Headers( {"Content-type:application/json;charset=UTF-8"} )
@GET( "/autopilot-car-hailing/evaluation/label/driver/taxi/list" )
Observable<TaxiPassengerAllStarWorld> getWorldAllStar(@Header ("appId") String appId, @Header("ticket") String ticket);
/**
* 获取星星对应的文案(和星星一一对应)
* @return
*/
@Headers( {"Content-type:application/json;charset=UTF-8"} )
@GET( "/autopilot-car-hailing/evaluation/label/driver/taxi/listByStar" )
Observable<TaxiPassengerAllStarWorld> getWorldByStar(@Header ("appId") String appId, @Header("ticket") String ticket,@Query("star") String star);
/**
* 查询司机是否已确认可开启自动驾驶
* @param appId

View File

@@ -11,7 +11,7 @@ import com.mogo.eagle.core.network.MoGoRetrofitFactory;
import com.mogo.eagle.core.network.RequestOptions;
import com.mogo.eagle.core.network.SubscribeImpl;
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger;
import com.mogo.module.common.MogoApisHandler;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerAllStarWorld;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerBaseRespBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerCheckPhoneUpdateOrderReqBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRemainingResp;
@@ -55,7 +55,7 @@ public class TaxiPassengerServiceManager {
*/
private String getDriverAppSn(){
if(DebugConfig.isDebug()){
return CallerTelematicManager.INSTANCE.getServerToken();
return "X20202206092431156";
}else {
return CallerTelematicManager.INSTANCE.getServerToken();
}
@@ -161,11 +161,20 @@ public class TaxiPassengerServiceManager {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSubscribeImpl(context, callback, "checkPhoneAndUpdateOrderStatus"));
}
public void arrivedAndScore(Context context, String orderNo,int star,TaxiPassengerServiceCallback<TaxiPassengerBaseRespBean> callback){
public void arrivedAndScore(Context context,TaxiPassengerScoreUpdateOrderReqBean taxiPassengerScoreUpdateOrderReqBean, TaxiPassengerServiceCallback<TaxiPassengerBaseRespBean> callback){
mOCHTaxiServiceApi.arrivedAndScore(
MoGoAiCloudClientConfig.getInstance().getServiceAppId()
,MoGoAiCloudClientConfig.getInstance().getToken()
,new TaxiPassengerScoreUpdateOrderReqBean(orderNo,star))
,taxiPassengerScoreUpdateOrderReqBean)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSubscribeImpl(context, callback, "checkPhoneAndUpdateOrderStatus"));
}
public void getAllScoreWorld(Context context,TaxiPassengerServiceCallback<TaxiPassengerAllStarWorld> callback){
mOCHTaxiServiceApi.getWorldAllStar(
MoGoAiCloudClientConfig.getInstance().getServiceAppId()
,MoGoAiCloudClientConfig.getInstance().getToken())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSubscribeImpl(context, callback, "arrivedAndScore"));
@@ -192,4 +201,13 @@ public class TaxiPassengerServiceManager {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSubscribeImpl(context,callback,"startServicePilotDone"));
}
public void getWorldByStar(Context context,String start,TaxiPassengerServiceCallback<TaxiPassengerAllStarWorld> callback){
mOCHTaxiServiceApi.getWorldByStar(
MoGoAiCloudClientConfig.getInstance().getServiceAppId()
,MoGoAiCloudClientConfig.getInstance().getToken(),start)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSubscribeImpl(context, callback, "checkPhoneAndUpdateOrderStatus"));
}
}

View File

@@ -14,6 +14,7 @@ import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener;
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger;
import com.mogo.eagle.core.utilcode.util.UiThreadHandler;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerScoreUpdateOrderReqBean;
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerADASStatusCallback;
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerControllerStatusCallback;
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerOrderStatusCallback;
@@ -232,11 +233,10 @@ public class BaseTaxiPassengerPresenter extends Presenter<TaxiPassengerBaseFragm
}
/**
*
* @param score 分数
* 封装请求
*/
public void arrivedAndScore(int score,String orderNo){
TaxiPassengerModel.getInstance().arrivedAndScore(score,orderNo, aBoolean -> mView.showArrivedEndLayout2Thank(aBoolean));
public void arrivedAndScore(TaxiPassengerScoreUpdateOrderReqBean taxiPassengerScoreUpdateOrderReqBean){
TaxiPassengerModel.getInstance().arrivedAndScore(taxiPassengerScoreUpdateOrderReqBean,aBoolean -> mView.showArrivedEndLayout2Thank(aBoolean));
}
/**

View File

@@ -28,6 +28,8 @@ import com.mogo.module.common.constants.DataTypes;
import com.mogo.och.taxi.passenger.R;
import com.mogo.och.taxi.passenger.callback.ITPClickStartAutopilotCallback;
import com.mogo.och.taxi.passenger.presenter.BaseTaxiPassengerPresenter;
import com.mogo.och.taxi.passenger.ui.comment.TaxiPassengerArrivedView;
import com.mogo.och.taxi.passenger.ui.leftmenu.OverlayLeftViewUtils;
import java.lang.ref.WeakReference;
@@ -119,19 +121,22 @@ public class TaxiPassengerBaseFragment extends MvpFragment<TaxiPassengerBaseFrag
MogoMapUIController.getInstance().changeMapVisualAngle(VisualAngleMode.MODE_LONG_SIGHT, null);
mMapswitchBtn.setImageResource(R.drawable.taxi_p_switch_map_long);
}
//OverlayLeftViewUtils.INSTANCE.dismissOverlayView();
}
});
findViewById(R.id.iv_temp).setOnClickListener(view -> {
OverlayLeftViewUtils.INSTANCE.showOverlayView(getActivity());
//showOrHideArrivedEndLayout(true, "北京北京北京", "1527481606997577728");
//showOrHidePressengerCheckPager(true, "开始站点开", "开始站点开始站点开始", "2", "京A888888", "18811539480");
//CallerHmiManager.INSTANCE.showToolsView();
//OCHFloatWindowManager.getInstance().ShowFloatWindow(getContext());
//OverlayViewUtils.showOverlayView(getActivity(),new TaxiPassengerMogoConsultView(getContext()));
});
}
private void initArrivedView(){
mArrivedEndView = new WeakReference<>(new TaxiPassengerArrivedView(getContext()));
mArrivedEndView.get().setITaxiPassengerScoreCallback((fraction, orderNo) -> getPresenter().arrivedAndScore(fraction,orderNo));
mArrivedEndView.get().setITaxiPassengerScoreCallback((taxiPassengerScoreUpdateOrderReqBean) -> getPresenter().arrivedAndScore(taxiPassengerScoreUpdateOrderReqBean));
}
private void initCheckView() {
@@ -248,6 +253,7 @@ public class TaxiPassengerBaseFragment extends MvpFragment<TaxiPassengerBaseFrag
if (ochServingOrderFragment == null){
ochServingOrderFragment = new TaxiPassengerServingOrderFragment().newInstance();
}
OverlayLeftViewUtils.INSTANCE.showOverlayView(getActivity());
if (ochServingOrderFragment.isHidden()){
transaction.show(ochServingOrderFragment).commitAllowingStateLoss();
return;
@@ -264,6 +270,7 @@ public class TaxiPassengerBaseFragment extends MvpFragment<TaxiPassengerBaseFrag
if (ochServingOrderFragment != null){
transaction.hide(ochServingOrderFragment).commitAllowingStateLoss();
}
OverlayLeftViewUtils.INSTANCE.dismissOverlayView();
}
}

View File

@@ -1,4 +1,4 @@
package com.mogo.och.taxi.passenger.ui
package com.mogo.och.taxi.passenger.ui.comment
import android.animation.*
import android.content.Context
@@ -11,16 +11,29 @@ import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.amap.api.navi.view.PoiInputSearchWidget
import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayoutManager
import com.google.android.flexbox.JustifyContent
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
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.OverlayViewUtils
import com.mogo.eagle.core.widget.media.video.SimpleVideoPlayer
import com.mogo.och.common.module.wigets.OCHBorderShadowLayout
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.bean.TaxiPassengerAllStarWorld
import com.mogo.och.taxi.passenger.bean.TaxiPassengerScoreUpdateOrderReqBean
import com.mogo.och.taxi.passenger.callback.ITaxiPassengerCommonCallback
import com.mogo.och.taxi.passenger.callback.ITaxiPassengerScoreCallback
import com.mogo.och.taxi.passenger.network.TaxiPassengerServiceCallback
import com.mogo.och.taxi.passenger.network.TaxiPassengerServiceManager
import com.mogo.och.taxi.passenger.ui.comment.adapter.CommentAdapter
import com.mogo.och.taxi.passenger.widget.ResizeAnimation
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
@@ -55,10 +68,14 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
private lateinit var ivStarFourth: ImageView
private lateinit var ivStarFifth: ImageView
private lateinit var ivAnimalList: ImageView
private lateinit var btnSubmit: AppCompatButton
private lateinit var rvCommentList: RecyclerView
private lateinit var acivClose: AppCompatImageView
private lateinit var svpFrame: SimpleVideoPlayer
private lateinit var clCommentContain: ConstraintLayout
private var subscribe: Disposable?=null
private var orderNo = ""
private var currentFraction = 1
private val gsyVideoOptionBuilder = GSYVideoOptionBuilder()
@@ -79,6 +96,7 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
private var allStartOrdered = mutableListOf<ImageView>()
var showThanks:Boolean = false
var allStarWithWorld: TaxiPassengerAllStarWorld?=null
private fun initView(context: Context) {
d(SceneConstant.M_TAXI_P + TAG, "initView")
@@ -90,6 +108,9 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
ivAnimalList = findViewById(R.id.iv_animal_list)
acivClose = findViewById(R.id.aciv_close)
svpFrame = findViewById(R.id.svp_frame)
rvCommentList = findViewById(R.id.rv_comment_list)
clCommentContain = findViewById(R.id.cl_comment_contain)
btnSubmit = findViewById(R.id.btn_submit)
svpFrame.setBackgroundResource(R.drawable.tail_ani_0000)
svpFrame.setIsTouchWiget(false)
svpFrame.setIsTouchWigetFull(false)
@@ -97,15 +118,11 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
svpFrame.enableDoubleClick = false
allStartOrdered = mutableListOf()
initCommentList()
initScore()
findViewById<View>(R.id.tv_please_score).setOnClickListener(this)
// debug 弹出
mArrivedEndStation.setOnLongClickListener {
scoreSuccess()
false
}
acivClose.setOnClickListener {
ochShadowLayout.visibility = View.GONE
@@ -125,6 +142,16 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_FULL)
}
private fun initCommentList() {
val recyclerVideoAdapter = CommentAdapter(context, mutableListOf())
rvCommentList.adapter = recyclerVideoAdapter
val manager = FlexboxLayoutManager(context)
manager.justifyContent = JustifyContent.CENTER
manager.flexWrap = FlexWrap.WRAP
rvCommentList.layoutManager = manager
btnSubmit.setOnClickListener(this)
}
private fun initScore() {
ivStarFirst = findViewById(R.id.iv_star_first)
ivStarSecond = findViewById(R.id.iv_star_second)
@@ -142,11 +169,20 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
allStartOrdered.add(ivStarThird)
allStartOrdered.add(ivStarFourth)
allStartOrdered.add(ivStarFifth)
// 请求文案
requestStarWord()
}
override fun onDetachedFromWindow() {
svpFrame.setBackgroundResource(R.drawable.tail_ani_0000)
tvFeel.text = ""
rvCommentList.visibility = View.INVISIBLE
btnSubmit.visibility = View.INVISIBLE
svpFrame.onVideoReset()
clCommentContain.getLayoutParams().height = 657
clCommentContain.requestLayout()
super.onDetachedFromWindow()
subscribe?.let {
if (!it.isDisposed) {
@@ -158,23 +194,45 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
override fun onClick(v: View?) {
when (v?.id) {
R.id.tv_please_score -> {
iTaxiPassengerScoreCallback?.onScoreCallback(2,orderNo)
//iTaxiPassengerScoreCallback?.onScoreCallback(2,orderNo)
}
R.id.iv_star_first -> {commitAndStartAnimation(1,"不满意")}
R.id.iv_star_second -> {commitAndStartAnimation(2,"不满意")}
R.id.iv_star_third -> {commitAndStartAnimation(3,"一般")}
R.id.iv_star_fourth -> {commitAndStartAnimation(4,"舒适")}
R.id.iv_star_fifth -> {commitAndStartAnimation(5,"舒适")}
R.id.btn_submit -> {submitScore()}
else -> {}
}
}
/**
* 提交到后台
*/
private fun submitScore() {
val commentAdapter = rvCommentList.adapter as CommentAdapter
val selectComment = commentAdapter.getSelectComment()
iTaxiPassengerScoreCallback?.onScoreCallback(TaxiPassengerScoreUpdateOrderReqBean(orderNo,currentFraction,selectComment))
}
private fun commitAndStartAnimation(fraction: Int,title:String) {
resetStar()
currentFraction = fraction
tvFeel.text = title
startStartAnimation(fraction)
allStartOrdered.forEach {
it.isEnabled = false
if(allStarWithWorld!=null&&allStarWithWorld!!.data!=null&&allStarWithWorld!!.data!!.size>0){
// 已经请求到总量了
val filter = allStarWithWorld!!.data.filter { it.star == fraction.toString() }
val commentAdapter = rvCommentList.adapter as CommentAdapter
commentAdapter.addAll(filter.toMutableList())
startStartAnimation(fraction)
}else{
// 总量请求失败 单独去取
requestStarWordByStar(fraction)
}
// allStartOrdered.forEach {
// it.isEnabled = false
// }
}
private var currentAnimarion = 0
private var maxIndex = 0
@@ -235,13 +293,36 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
set.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
iTaxiPassengerScoreCallback?.onScoreCallback(fraction,orderNo)
// 启动变高动画
startChangeHeightAnimarion()
}
})
}
set.start()
}
private fun startChangeHeightAnimarion() {
// 815 除了 点评的高度
val resizeAnimation = ResizeAnimation(clCommentContain,815+rvCommentList.height, clCommentContain.height)
resizeAnimation.duration = 300
resizeAnimation.setAnimationListener(object :Animation.AnimationListener{
override fun onAnimationStart(animation: Animation?) {
rvCommentList.visibility = View.VISIBLE
btnSubmit.visibility = View.VISIBLE
}
override fun onAnimationEnd(animation: Animation?) {
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
clCommentContain.startAnimation(resizeAnimation)
}
/**
* 设置目的地重置星星状态
*/
@@ -316,6 +397,59 @@ class TaxiPassengerArrivedView :RelativeLayout, View.OnClickListener {
}
}
private fun requestStarWord() {
TaxiPassengerServiceManager.getInstance().getAllScoreWorld(context,
object : TaxiPassengerServiceCallback<TaxiPassengerAllStarWorld?> {
override fun onError() {
CallerLogger.e(
SceneConstant.M_TAXI_P + TAG,
"/autopilot-car-hailing/evaluation/label/driver/taxi/list 接口 onError"
)
}
override fun onFail(code: Int, msg: String) {
CallerLogger.e(
SceneConstant.M_TAXI_P + TAG,
"/autopilot-car-hailing/evaluation/label/driver/taxi/list 接口:${code}---${msg}"
)
}
override fun onSuccess(data: TaxiPassengerAllStarWorld?) {
allStarWithWorld = data
}
})
}
private fun requestStarWordByStar(start:Int) {
TaxiPassengerServiceManager.getInstance().getWorldByStar(context,start.toString(),
object : TaxiPassengerServiceCallback<TaxiPassengerAllStarWorld?> {
override fun onError() {
CallerLogger.e(
SceneConstant.M_TAXI_P + TAG,
"/autopilot-car-hailing/evaluation/label/driver/taxi/listByStar 接口 onError"
)
}
override fun onFail(code: Int, msg: String) {
CallerLogger.e(
SceneConstant.M_TAXI_P + TAG,
"/autopilot-car-hailing/evaluation/label/driver/taxi/listByStar 接口:${code}---${msg}"
)
}
override fun onSuccess(data: TaxiPassengerAllStarWorld?) {
if(data?.data != null &&data.data!!.size>0){
val commentAdapter = rvCommentList.adapter as CommentAdapter
commentAdapter.addAll(data.data.toMutableList())
startStartAnimation(start)
}
}
})
}
companion object {
const val TAG = "TaxiPassengerArrivedView"
}

View File

@@ -0,0 +1,56 @@
package com.mogo.och.taxi.passenger.ui.comment.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.bean.TaxiPassengerAllStarWorld.TaxiPassengerStarWorld
class CommentAdapter(private val context: Context?,private val itemDataList: MutableList<TaxiPassengerStarWorld>) :
RecyclerView.Adapter<ItemCommentHolder>() {
fun add(taxiPassengerStarWorld: TaxiPassengerStarWorld) {
itemDataList.add(taxiPassengerStarWorld)
notifyItemInserted(itemDataList.size)
}
fun addAll(itemDataList: MutableList<TaxiPassengerStarWorld>){
this.itemDataList.clear()
this.itemDataList.addAll(itemDataList)
this.notifyDataSetChanged()
}
fun getSelectComment(): List<TaxiPassengerStarWorld> {
return itemDataList.filter { it.isSelect }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemCommentHolder {
val v = LayoutInflater.from(context).inflate(R.layout.list_comment_item, parent, false)
return ItemCommentHolder(context, v)
}
override fun onBindViewHolder(holder: ItemCommentHolder, position: Int) {
val taxiPassengerStarWorld = itemDataList[position]
holder.commentItem.text = taxiPassengerStarWorld.labelInfo
if (taxiPassengerStarWorld.isSelect!=null&&taxiPassengerStarWorld.isSelect) {
taxiPassengerStarWorld.isSelect = true
holder.commentItem.setBackgroundResource(R.drawable.taxi_p_comment_selected)
} else {
taxiPassengerStarWorld.isSelect = false
holder.commentItem.setBackgroundResource(R.drawable.taxi_p_comment_select)
}
holder.commentItem.setOnClickListener { v: View? ->
taxiPassengerStarWorld.isSelect = !taxiPassengerStarWorld.isSelect
notifyItemChanged(holder.bindingAdapterPosition)
}
}
override fun getItemCount(): Int {
return itemDataList.size
}
companion object {
private const val TAG = "RecyclerBaseAdapter"
}
}

View File

@@ -0,0 +1,25 @@
package com.mogo.och.taxi.passenger.ui.comment.adapter;
import android.content.Context;
import android.view.View;
import android.widget.CheckBox;
import androidx.recyclerview.widget.RecyclerView;
import com.mogo.och.taxi.passenger.R;
public class ItemCommentHolder extends RecyclerView.ViewHolder {
public final static String TAG = "ItemCommentHolder";
protected Context context;
public CheckBox commentItem;
public ItemCommentHolder(Context context, View v) {
super(v);
this.context = context;
commentItem = v.findViewById(R.id.tv_comment);
}
}

View File

@@ -0,0 +1,75 @@
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 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
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
x = motionEvent.rawX.toInt()
dragTime = System.currentTimeMillis()
}
MotionEvent.ACTION_MOVE -> {
val nowX = motionEvent.rawX.toInt()
val movedX = nowX - x
x = nowX
wl.apply {
x += movedX
}
if (wl.x > 0 || wl.x < OverlayLeftViewUtils.DEVIATION_WIDTH) {
wl.apply {
x -= movedX
}
return false
}
if (wl.x > NEGATIVEDEVIATION && movedX > 0) {
open(view.rootView,windowManager)
}else{
//更新悬浮球控件位置
windowManager?.updateViewLayout(view.rootView, wl)
}
if (wl.x < OverlayLeftViewUtils.DEVIATION_WIDTH +DEVIATION && movedX < 0) {
close(view.rootView,windowManager)
}else{
//更新悬浮球控件位置
windowManager?.updateViewLayout(view.rootView, wl)
}
}
MotionEvent.ACTION_UP -> {
val startX = wl.x
if (startX > OverlayLeftViewUtils.DEVIATION_WIDTH /2 && startX < 0) {
//拖动距离大于一半 自动打开
open(view.rootView,windowManager)
} else if (startX < OverlayLeftViewUtils.DEVIATION_WIDTH /2 && startX >= OverlayLeftViewUtils.DEVIATION_WIDTH) {
// 拖动距离小于一半自动关闭
close(view.rootView,windowManager)
}
if (System.currentTimeMillis() - dragTime > 500) {
dragTime = 0
return true
}
}
else -> {
}
}
return false
}
}

View File

@@ -0,0 +1,55 @@
package com.mogo.och.taxi.passenger.ui.leftmenu
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
import com.mogo.och.taxi.passenger.ui.leftmenu.model.LeftMenuModel
class ListAdapter(private val context: Context,val list: MutableList<LeftMenuModel>) : BaseAdapter() {
override fun getCount(): Int {
return list.size
}
override fun getItem(position: Int): Any {
return list[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val imageView = ImageView(context)
val leftMenuModel = list[position]
if (leftMenuModel.isChecked) {
imageView.setImageResource(leftMenuModel.selected)
} else {
imageView.setImageResource(leftMenuModel.select)
}
imageView.setOnClickListener {
for (i in list.indices) {
if(position==i){
if(!list[i].isChecked){
list[i].selectListener.onSelect(convertView)
}
}
list[i].isChecked = position == i
}
notifyDataSetChanged()
}
return imageView
}
interface OnTabSelectListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
fun onSelect(v: View?)
}
}

View File

@@ -0,0 +1,260 @@
package com.mogo.och.taxi.passenger.ui.leftmenu
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.Region
import android.view.*
import android.widget.ListView
import androidx.constraintlayout.widget.ConstraintLayout
import com.mogo.eagle.core.utilcode.util.OverlayViewUtils
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.ui.leftmenu.model.LeftMenuModel
import com.mogo.och.taxi.passenger.ui.video.TaxiPassengerMogoConsultView
import com.mogo.och.taxi.passenger.ui.video.TaxiPassengerMogoMoviesView
import com.mogo.och.taxi.passenger.utils.windowdispatch.OnComputeInternalInsetsListener
import com.mogo.och.taxi.passenger.utils.windowdispatch.ReflectionUtils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit
/**
* 遮罩层工具类
*
* @author mogoauto
*/
@SuppressLint("StaticFieldLeak")
object OverlayLeftViewUtils {
private const val TAG = "OverlayViewUtils"
private var windowManager: WindowManager? = null
private var applicationContext: Context? = null
@Volatile
private var isShowing = false
private var mInvocationHandler: OnComputeInternalInsetsListener? = null
private val mTouchRegion = Region()
private var params:WindowManager.LayoutParams?=null
const val WIDTH = 810
const val DEVIATION_WIDTH = -669
private var overlayView: View?=null
private var subscribe: Disposable?=null
private var taxiPassengerMogoConsultView: WeakReference<TaxiPassengerMogoConsultView>? = null
private var taxiPassengerMogoMoviesView: WeakReference<TaxiPassengerMogoMoviesView>? = null
/**
* 添加覆盖View在Activity上面
*/
@JvmOverloads
fun showOverlayView(context: Activity, ani: Int = -1) {
if (isShowing) {
return
}
if (applicationContext == null) {
applicationContext = context.applicationContext
}
if (windowManager == null) {
windowManager = context.windowManager
}
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,view)
// 如果正在展示中并且lastOverlayView不为null先做移除操作保证覆盖在最上面的View只有一个防止叠加导致无法移除
dismissOverlayView()
val vDragField = view.findViewById<View>(R.id.v_drag_field)
vDragField.setOnTouchListener(ItemViewTouchListener(params!!, windowManager, ::close,
::open))
vDragField.setOnClickListener {
val start: Int = params!!.x
if (start > DEVIATION_WIDTH /2 && start < 10) {
close(view, windowManager)
} else if (start < DEVIATION_WIDTH /2 && start >= DEVIATION_WIDTH) {
open(view, windowManager)
}
}
val lvSelectItem = view.findViewById<ListView>(R.id.lv_select_item)
val integers = mutableListOf<LeftMenuModel>()
val liveSelected = object :ListAdapter.OnTabSelectListener{
override fun onSelect(v: View?) {
close(view, windowManager)
if(taxiPassengerMogoConsultView?.get() != null){
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoConsultView?.get())
}
if(taxiPassengerMogoMoviesView?.get() != null){
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoMoviesView?.get())
}
}
}
val consultSelect = object :ListAdapter.OnTabSelectListener{
override fun onSelect(v: View?) {
close(view, windowManager)
if(taxiPassengerMogoMoviesView?.get() != null){
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoMoviesView?.get())
}
if(taxiPassengerMogoConsultView?.get() != null){
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoConsultView?.get())
}else{
taxiPassengerMogoConsultView =
WeakReference<TaxiPassengerMogoConsultView>(TaxiPassengerMogoConsultView(context))
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoConsultView?.get())
}
}
}
val entertainmentSelect = object :ListAdapter.OnTabSelectListener{
override fun onSelect(v: View?) {
close(view, windowManager)
if(taxiPassengerMogoConsultView?.get() != null){
OverlayViewUtils.dismissOverlayView(taxiPassengerMogoConsultView?.get())
}
if(taxiPassengerMogoMoviesView?.get() != null){
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoMoviesView?.get())
}else{
taxiPassengerMogoMoviesView =
WeakReference<TaxiPassengerMogoMoviesView>(TaxiPassengerMogoMoviesView(context))
OverlayViewUtils.showOverlayView(context,taxiPassengerMogoMoviesView?.get())
}
}
}
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_live_select,R.drawable.taxi_p_mogo_live_selected,true,liveSelected))
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_consult_select,R.drawable.taxi_p_mogo_consult_selected,false,consultSelect))
integers.add(LeftMenuModel(R.drawable.taxi_p_mogo_entertainment_select,R.drawable.taxi_p_mogo_entertainment_selected,false,entertainmentSelect))
lvSelectItem.adapter = ListAdapter(context, integers)
view.viewTreeObserver
.addOnGlobalLayoutListener(object :ViewTreeObserver.OnGlobalLayoutListener{
override fun onGlobalLayout() {
mTouchRegion.setEmpty()
mTouchRegion.op(getViewBounds(vDragField), Region.Op.UNION)
mTouchRegion.op(getViewBounds(lvSelectItem), Region.Op.UNION)
mInvocationHandler?.touchRegion = mTouchRegion
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
try {
mInvocationHandler =
OnComputeInternalInsetsListener()
ReflectionUtils.removeOnComputeInternalInsetsListener(view.viewTreeObserver)
ReflectionUtils.addOnComputeInternalInsetsListener(
view.viewTreeObserver,
mInvocationHandler?.getListener()
)
mInvocationHandler?.touchRegion = mTouchRegion
windowManager!!.addView(overlayView, params)
isShowing = true
} catch (e: Exception) {
e.printStackTrace()
}
}
}
/**
* 打开状态栏
*/
private fun open(overlayView: View,windowManager: WindowManager?) {
params?.x = 0
windowManager?.updateViewLayout(overlayView, params)
closeByTime(overlayView, windowManager)
}
private fun closeByTime(
overlayView: View,
windowManager: WindowManager?
) {
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,view :View) {
if(params ==null) {
params = WindowManager.LayoutParams()
}
params?.let {
it.width = WIDTH
it.height = WindowManager.LayoutParams.MATCH_PARENT
it.alpha = 1.0f
it.gravity = Gravity.START or Gravity.TOP
it.x = 0
it.y = 0
it.format = PixelFormat.RGBA_8888
// 设置窗口类型为应用子窗口和PopupWindow同类型
it.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL
// 没有边界限制,允许窗口扩展到屏幕外
it.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
if (ani != -1) {
it.windowAnimations = ani
}
closeByTime(view, windowManager)
}
}
/**
* 移除覆盖View在Activity上面
*/
fun dismissOverlayView() {
if (!isShowing) {
return
}
subscribe?.let {
if (!it.isDisposed) {
it.dispose()
}
}
try {
if (windowManager != null && overlayView != null) {
windowManager!!.removeView(overlayView)
}
isShowing = false
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getViewBounds(view: View): Rect {
return Rect(view.left, view.top, view.right, view.bottom)
}
}

View File

@@ -0,0 +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,
val selectListener: ListAdapter.OnTabSelectListener
)

View File

@@ -0,0 +1,88 @@
package com.mogo.och.taxi.passenger.ui.video
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.view.View
import android.view.WindowManager
import com.mogo.och.taxi.passenger.ui.video.FullVideoUtils
import java.lang.Exception
/**
* 遮罩层工具类
*
* @author mogoauto
*/
@SuppressLint("StaticFieldLeak")
object FullVideoUtils {
private const val TAG = "OverlayViewUtils"
private var windowManager: WindowManager? = null
private var applicationContext: Context? = null
@Volatile
private var isShowing = false
/**
* 记录上一次的View
*/
private var lastOverlayView: View? = null
/**
* 添加覆盖View在Activity上面
*/
@JvmOverloads
fun showOverlayView(context: Activity, overlayView: View, ani: Int = -1) {
if (applicationContext == null) {
applicationContext = context.applicationContext
}
if (windowManager == null) {
windowManager = context.windowManager
}
// 设置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)
val params = WindowManager.LayoutParams()
params.width = WindowManager.LayoutParams.MATCH_PARENT
params.height = WindowManager.LayoutParams.MATCH_PARENT
params.alpha = 1.0f
// 设置窗口类型为应用子窗口和PopupWindow同类型
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
// 没有边界限制,允许窗口扩展到屏幕外
params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
if (ani != -1) {
params.windowAnimations = ani
}
try {
// 后门逻辑,长时间触摸消失
lastOverlayView = overlayView
windowManager!!.addView(overlayView, params)
isShowing = true
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 移除覆盖View在Activity上面
*/
fun dismissOverlayView() {
if (!isShowing) {
return
}
try {
if (windowManager != null) {
windowManager!!.removeView(lastOverlayView)
}
if (lastOverlayView != null) {
lastOverlayView = null
}
isShowing = false
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,31 @@
package com.mogo.och.taxi.passenger.ui.video;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.widget.ImageView;
import androidx.recyclerview.widget.RecyclerView;
import com.mogo.och.taxi.passenger.R;
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer;
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder;
public class RecyclerItemVideoHolder extends RecyclerView.ViewHolder {
public final static String TAG = "RecyclerView2List";
protected Context context;
public ConsultVideoPlayer gsyVideoPlayer;
GSYVideoOptionBuilder gsyVideoOptionBuilder;
public RecyclerItemVideoHolder(Context context, View v) {
super(v);
this.context = context;
gsyVideoPlayer = v.findViewById(R.id.video_item_player);
gsyVideoOptionBuilder = new GSYVideoOptionBuilder();
}
}

View File

@@ -0,0 +1,98 @@
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;
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.utilcode.util.ToastUtils;
import com.mogo.och.taxi.passenger.R;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerVideoPlay;
import java.util.List;
public class RecyclerVideoAdapter extends RecyclerView.Adapter<RecyclerItemVideoHolder> {
private final static String TAG = "RecyclerBaseAdapter";
private List<TaxiPassengerVideoPlay> itemDataList = null;
private Context context = null;
private OnThumbImageClilckListener onThumbImageClilckListener;
public OnThumbImageClilckListener getOnThumbImageClilckListener() {
return onThumbImageClilckListener;
}
public void setOnThumbImageClilckListener(OnThumbImageClilckListener onThumbImageClilckListener) {
this.onThumbImageClilckListener = onThumbImageClilckListener;
}
public RecyclerVideoAdapter(Context context, List<TaxiPassengerVideoPlay> itemDataList) {
this.itemDataList = itemDataList;
this.context = context;
}
public TaxiPassengerVideoPlay getItemByPosition(int position){
if(itemDataList!=null){
return itemDataList.get(position);
}
return null;
}
@NonNull
@Override
public RecyclerItemVideoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.list_video_item_normal, parent, false);
RecyclerItemVideoHolder recyclerItemVideoHolder = new RecyclerItemVideoHolder(context, v);
recyclerItemVideoHolder.setIsRecyclable(false);
return recyclerItemVideoHolder;
}
@Override
public void onBindViewHolder(@NonNull final RecyclerItemVideoHolder holder, int position) {
final TaxiPassengerVideoPlay taxiPassengerVideoPlay = itemDataList.get(position);
holder.gsyVideoOptionBuilder
.setEnlargeImageRes(R.drawable.taxi_p_change_full)
.setUrl(taxiPassengerVideoPlay.getUrl())
.setCacheWithPlay(true)
.setPlayTag(taxiPassengerVideoPlay.getImageUrl()+position)
.setThumbImageView(holder.gsyVideoPlayer.coverImage)
.setThumbPlay(false)
.build(holder.gsyVideoPlayer);
holder.gsyVideoPlayer.setTitle(taxiPassengerVideoPlay.getTitle());
Glide.with(context)
.load(taxiPassengerVideoPlay.getImageUrl())
.apply(new RequestOptions().placeholder(R.drawable.taxi_p_video_holder).centerCrop())
.into(holder.gsyVideoPlayer.coverImage);
holder.gsyVideoPlayer.getThumbImageView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(onThumbImageClilckListener!=null){
onThumbImageClilckListener.onDxChanged(holder.getAbsoluteAdapterPosition());
}
}
});
}
@Override
public int getItemCount() {
return itemDataList.size();
}
@Override
public int getItemViewType(int position) {
return 1;
}
public interface OnThumbImageClilckListener {
void onDxChanged(int targetPosition);
}
}

View File

@@ -0,0 +1,254 @@
package com.mogo.och.taxi.passenger.ui.video
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
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.utils.blur.GlideBlurTransform
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer
import com.mogo.och.taxi.passenger.widget.indicator.IndicatorView
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorStyle
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import java.util.*
import kotlin.math.floor
/**
*
* 蘑菇咨询
* Created on 2022/5/16
*/
class TaxiPassengerMogoConsultView :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 indicatorView: IndicatorView
private lateinit var clContain: ConstraintLayout
private fun initView(context: Context) {
LayoutInflater.from(context).inflate(R.layout.taxi_p_arrived_mogo_consult, this, true)
rvVideoPlaylist = findViewById(R.id.rv_video_playlist)
indicatorView = findViewById(R.id.indicatorView)
clContain = findViewById(R.id.cl_contain)
val arrayListOf = ArrayList<TaxiPassengerVideoPlay>()
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708409810/20210610重新排版3屏.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969579713/三屏.png","重新排版"))
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708499497/大运会合作解说版.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969536177/大运会.png","大运会"))
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708554279/红旗车队.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969553174/红旗重新排版.png","红旗车队"))
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708596763/全车型混剪增加红旗车队.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969511280/车队.png","全车型混剪增加红旗车队"))
val recyclerVideoAdapter = RecyclerVideoAdapter(context, arrayListOf)
val carouselLayoutManager = CarouselLayoutManager(CarouselLayoutManager.HORIZONTAL, true)
carouselLayoutManager.setPostLayoutListener(CarouselZoomPostLayoutListener ())
carouselLayoutManager.maxVisibleItems = 1
indicatorView.notifyDataChanged(arrayListOf.size)
indicatorView.setSlideMode(IndicatorSlideMode.SCALE)
indicatorView.setOrientation(IndicatorOrientation.INDICATOR_HORIZONTAL)
indicatorView.setIndicatorStyle(IndicatorStyle.ROUND_RECT)
indicatorView.setSliderColor(Color.parseColor("#80FFFFFF"), Color.parseColor("#2972FF"))
indicatorView.setSliderWidth(14f, 90f)
indicatorView.setSliderHeight(14f)
rvVideoPlaylist.addOnScrollListener(object: CenterScrollListener() {
var prePlayerPosition = 0
override fun pageSelect(recyclerView: RecyclerView?, newState: Int) {
//播放视频
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
indicatorView.onPageSelected(centerItemPosition)
if(player is ConsultVideoPlayer){
if(prePlayerPosition!=centerItemPosition) {
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)
}
}
prePlayerPosition = centerItemPosition
}
override fun pageStop() {
val centerItemPosition: Int = carouselLayoutManager.getCenterItemPosition()
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
if(player is ConsultVideoPlayer){
player.onVideoPause()
}
}
})
carouselLayoutManager.addOnDargAutoDiffListener { adapterPosition, currentPosition ->
val fl = adapterPosition - floor(adapterPosition)
var currentIndex = currentPosition
if(fl>0.5){
if(currentPosition==0){
currentIndex = rvVideoPlaylist.adapter!!.itemCount
}else {
currentIndex -= 1
}
}
indicatorView.onPageScrolled(currentIndex, fl, 0)
}
recyclerVideoAdapter.setOnThumbImageClilckListener {
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
if(player is ConsultVideoPlayer) {
player.onVideoReset()
player.thumbImageViewLayout.visibility = View.VISIBLE
}
rvVideoPlaylist.smoothScrollToPosition(it)
}
rvVideoPlaylist.layoutManager = carouselLayoutManager
rvVideoPlaylist.setHasFixedSize(true)
rvVideoPlaylist.adapter = recyclerVideoAdapter
}
private fun setBackageAndPlayNext(
taxiPassengerVideoPlay: TaxiPassengerVideoPlay,
player: ConsultVideoPlayer,
centerItemPosition: Int,
) {
// 设置背景图片
Glide.with(context).asBitmap()
.load(taxiPassengerVideoPlay.imageUrl)
.apply(
RequestOptions().transform(
GlideBlurTransform(
context,
taxiPassengerVideoPlay.imageUrl,
5
)
)
)
.into(object : SimpleTarget<Bitmap?>() {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap?>?
) {
clContain.background = BitmapDrawable(context.resources, resource)
}
})
if(player.getVideoAllCallBack()==null) {
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 onClickBlank(url: String?, vararg objects: Any?) {
super.onClickBlank(url, *objects)
rvVideoPlaylist.smoothScrollToPosition(centerItemPosition)
}
})
}
}
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){// 获取焦点两种情况
// 恢复播放和开始播放
if(player.isIfCurrentIsFullscreen){// 全屏了
}else {
when (player.currentState) {
GSYVideoView.CURRENT_STATE_PAUSE -> {
player.onVideoResume(false)
}
GSYVideoView.CURRENT_STATE_PLAYING -> {
}
else -> {
val recyclerVideoAdapter =
rvVideoPlaylist.adapter as RecyclerVideoAdapter
setBackageAndPlayNext(
recyclerVideoAdapter.getItemByPosition(centerItemPosition),
player, centerItemPosition
)
//player.startPlayLogic()
}
}
}
}else {
// 离开应用 暂停视频
// 关闭 onDetachedFromWindow 会reset
if(player.isIfCurrentIsFullscreen){// 全屏了
}else {
player.onVideoPause()
}
}
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
val carouselLayoutManager = rvVideoPlaylist.layoutManager as CarouselLayoutManager
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
player?.let {
if(player is ConsultVideoPlayer){
player.release()
}
}
}
companion object {
const val TAG = "TaxiPassengerMogoConsultView"
}
init {
try {
initView(context)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,237 @@
package com.mogo.och.taxi.passenger.ui.video
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
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.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.utils.blur.GlideBlurTransform
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer
import com.mogo.och.taxi.passenger.widget.indicator.IndicatorView
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorStyle
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import java.util.*
import kotlin.math.floor
/**
*
* 蘑菇咨询
* 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 indicatorView: IndicatorView
private lateinit var clContain: ConstraintLayout
private fun initView(context: Context) {
LayoutInflater.from(context).inflate(R.layout.taxi_p_mogo_movies, this, true)
rvVideoPlaylist = findViewById(R.id.rv_video_playlist)
indicatorView = findViewById(R.id.indicatorView)
clContain = findViewById(R.id.cl_contain)
val arrayListOf = ArrayList<TaxiPassengerVideoPlay>()
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708409810/20210610重新排版3屏.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969579713/三屏.png","重新排版"))
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708499497/大运会合作解说版.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969536177/大运会.png","大运会"))
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708554279/红旗车队.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969553174/红旗重新排版.png","红旗车队"))
arrayListOf.add(TaxiPassengerVideoPlay("https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708596763/全车型混剪增加红旗车队.m4v","https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969511280/车队.png","全车型混剪增加红旗车队"))
val recyclerVideoAdapter = RecyclerVideoAdapter(context, arrayListOf)
val carouselLayoutManager = CarouselLayoutManager(CarouselLayoutManager.HORIZONTAL, true)
carouselLayoutManager.setPostLayoutListener(CarouselZoomPostLayoutListener ())
carouselLayoutManager.maxVisibleItems = 1
indicatorView.notifyDataChanged(arrayListOf.size)
indicatorView.setSlideMode(IndicatorSlideMode.SCALE)
indicatorView.setOrientation(IndicatorOrientation.INDICATOR_HORIZONTAL)
indicatorView.setIndicatorStyle(IndicatorStyle.ROUND_RECT)
indicatorView.setSliderColor(Color.parseColor("#80FFFFFF"), Color.parseColor("#2972FF"))
indicatorView.setSliderWidth(14f, 90f)
indicatorView.setSliderHeight(14f)
rvVideoPlaylist.addOnScrollListener(object: CenterScrollListener() {
var prePlayerPosition = 0
override fun pageSelect(recyclerView: RecyclerView?, newState: Int) {
//播放视频
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
indicatorView.onPageSelected(centerItemPosition)
if(player is ConsultVideoPlayer){
if(prePlayerPosition!=centerItemPosition) {
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)
}
}
prePlayerPosition = centerItemPosition
}
override fun pageStop() {
val centerItemPosition: Int = carouselLayoutManager.getCenterItemPosition()
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
if(player is ConsultVideoPlayer){
player.onVideoPause()
}
}
})
carouselLayoutManager.addOnDargAutoDiffListener { adapterPosition, currentPosition ->
val fl = adapterPosition - floor(adapterPosition)
var currentIndex = currentPosition
if(fl>0.5){
if(currentPosition==0){
currentIndex = rvVideoPlaylist.adapter!!.itemCount
}else {
currentIndex -= 1
}
}
indicatorView.onPageScrolled(currentIndex, fl, 0)
}
rvVideoPlaylist.layoutManager = carouselLayoutManager
rvVideoPlaylist.setHasFixedSize(true)
rvVideoPlaylist.adapter = recyclerVideoAdapter
}
private fun setBackageAndPlayNext(
taxiPassengerVideoPlay: TaxiPassengerVideoPlay,
player: ConsultVideoPlayer,
centerItemPosition: Int,
) {
// 设置背景图片
Glide.with(context).asBitmap()
.load(taxiPassengerVideoPlay.imageUrl)
.apply(
RequestOptions().transform(
GlideBlurTransform(
context,
taxiPassengerVideoPlay.imageUrl,
5
)
)
)
.into(object : SimpleTarget<Bitmap?>() {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap?>?
) {
clContain.background = BitmapDrawable(context.resources, resource)
}
})
if(player.getVideoAllCallBack()==null) {
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){// 获取焦点两种情况
// 恢复播放和开始播放
if(player.isIfCurrentIsFullscreen){// 全屏了
}else {
when (player.currentState) {
GSYVideoView.CURRENT_STATE_PAUSE -> {
player.onVideoResume(false)
}
GSYVideoView.CURRENT_STATE_PLAYING -> {
}
else -> {
val recyclerVideoAdapter =
rvVideoPlaylist.adapter as RecyclerVideoAdapter
setBackageAndPlayNext(
recyclerVideoAdapter.getItemByPosition(centerItemPosition),
player, centerItemPosition
)
player.startPlayLogic()
}
}
}
}else {
// 离开应用 暂停视频
// 关闭 onDetachedFromWindow 会reset
if(player.isIfCurrentIsFullscreen){// 全屏了
}else {
player.onVideoPause()
}
}
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
val carouselLayoutManager = rvVideoPlaylist.layoutManager as CarouselLayoutManager
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
val player = carouselLayoutManager.findViewByPosition(centerItemPosition)
player?.let {
if(player is ConsultVideoPlayer){
player.release()
}
}
}
companion object {
const val TAG = "TaxiPassengerMogoConsultView"
}
init {
try {
initView(context)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,964 @@
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
import android.graphics.PointF;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.OrientationHelper;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* An implementation of {@link RecyclerView.LayoutManager} that layout items like carousel.
* Generally there is one center item and bellow this item there are maximum {@link CarouselLayoutManager#getMaxVisibleItems()} items on each side of the center
* item. By default {@link CarouselLayoutManager#getMaxVisibleItems()} is {@link CarouselLayoutManager#MAX_VISIBLE_ITEMS}.<br />
* <br />
* This LayoutManager supports only fixedSized adapter items.<br />
* <br />
* This LayoutManager supports {@link CarouselLayoutManager#HORIZONTAL} and {@link CarouselLayoutManager#VERTICAL} orientations. <br />
* <br />
* This LayoutManager supports circle layout. By default it if disabled. We don't recommend to use circle layout with adapter items count less then 3. <br />
* <br />
* Please be sure that layout_width of adapter item is a constant value and not {@link ViewGroup.LayoutParams#MATCH_PARENT}
* for {@link #HORIZONTAL} orientation.
* So like layout_height is not {@link ViewGroup.LayoutParams#MATCH_PARENT} for {@link CarouselLayoutManager#VERTICAL}<br />
* <br />
*/
public class CarouselLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider {
public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
public static final int VERTICAL = OrientationHelper.VERTICAL;
/**
* 固定值一直不变
*/
public static final int INVALID_POSITION = -1;
public static final int MAX_VISIBLE_ITEMS = 3;
private static final boolean CIRCLE_LAYOUT = false;
private boolean mDecoratedChildSizeInvalid;
private Integer mDecoratedChildWidth;
private Integer mDecoratedChildHeight;
private final int mOrientation;
private boolean mCircleLayout;
private int mPendingScrollPosition;
private final LayoutHelper mLayoutHelper = new LayoutHelper(MAX_VISIBLE_ITEMS);
private PostLayoutListener mViewPostLayout;
private final List<OnCenterItemSelectionListener> mOnCenterItemSelectionListeners = new ArrayList<>();
private final List<OnDargAutoDiffListener> onDargAutoDiffListeners = new ArrayList<>();
private int mCenterItemPosition = INVALID_POSITION;
private int mItemsCount;
@Nullable
private CarouselSavedState mPendingCarouselSavedState;
/**
* @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
*/
@SuppressWarnings("unused")
public CarouselLayoutManager(final int orientation) {
this(orientation, CIRCLE_LAYOUT);
}
/**
* If circleLayout is true then all items will be in cycle. Scroll will be infinite on both sides.
*
* @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
* @param circleLayout true for enabling circleLayout
*/
@SuppressWarnings("unused")
public CarouselLayoutManager(final int orientation, final boolean circleLayout) {
if (HORIZONTAL != orientation && VERTICAL != orientation) {
throw new IllegalArgumentException("orientation should be HORIZONTAL or VERTICAL");
}
mOrientation = orientation;
mCircleLayout = circleLayout;
mPendingScrollPosition = INVALID_POSITION;
}
/**
* Change circle layout type
*/
@SuppressWarnings("unused")
public void setCircleLayout(final boolean circleLayout) {
if (mCircleLayout != circleLayout) {
mCircleLayout = circleLayout;
requestLayout();
}
}
/**
* Setup {@link PostLayoutListener} for this LayoutManager.
* Its methods will be called for each visible view item after general LayoutManager layout finishes. <br />
* <br />
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
*
* @param postLayoutListener listener for item layout changes. Can be null.
*/
@SuppressWarnings("unused")
public void setPostLayoutListener(@Nullable final PostLayoutListener postLayoutListener) {
mViewPostLayout = postLayoutListener;
requestLayout();
}
/**
* Setup maximum visible (layout) items on each side of the center item.
* Basically during scrolling there can be more visible items (+1 item on each side), but in idle state this is the only reached maximum.
*
* @param maxVisibleItems should be great then 0, if bot an {@link IllegalAccessException} will be thrown
*/
@CallSuper
@SuppressWarnings("unused")
public void setMaxVisibleItems(final int maxVisibleItems) {
if (0 > maxVisibleItems) {
throw new IllegalArgumentException("maxVisibleItems can't be less then 0");
}
mLayoutHelper.mMaxVisibleItems = maxVisibleItems;
requestLayout();
}
/**
* @return current setup for maximum visible items.
* @see #setMaxVisibleItems(int)
*/
@SuppressWarnings("unused")
public int getMaxVisibleItems() {
return mLayoutHelper.mMaxVisibleItems;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* @return current layout orientation
* @see #VERTICAL
* @see #HORIZONTAL
*/
public int getOrientation() {
return mOrientation;
}
@Override
public boolean canScrollHorizontally() {
return 0 != getChildCount() && HORIZONTAL == mOrientation;
}
@Override
public boolean canScrollVertically() {
return 0 != getChildCount() && VERTICAL == mOrientation;
}
/**
* @return current layout center item
*/
public int getCenterItemPosition() {
return mCenterItemPosition;
}
/**
* @param onCenterItemSelectionListener listener that will trigger when ItemSelectionChanges. can't be null
*/
public void addOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) {
mOnCenterItemSelectionListeners.add(onCenterItemSelectionListener);
}
/**
* @param onCenterItemSelectionListener listener that was previously added by {@link #addOnItemSelectionListener(OnCenterItemSelectionListener)}
*/
public void removeOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) {
mOnCenterItemSelectionListeners.remove(onCenterItemSelectionListener);
}
public void addOnDargAutoDiffListener(@NonNull final OnDargAutoDiffListener onDargAutoDiffListener) {
onDargAutoDiffListeners.add(onDargAutoDiffListener);
}
public void removeOnDargAutoDiffListener(@NonNull final OnDargAutoDiffListener onDargAutoDiffListener) {
onDargAutoDiffListeners.remove(onDargAutoDiffListener);
}
@SuppressWarnings("RefusedBequest")
@Override
public void scrollToPosition(final int position) {
if (0 > position) {
throw new IllegalArgumentException("position can't be less then 0. position is : " + position);
}
mPendingScrollPosition = position;
requestLayout();
}
@SuppressWarnings("RefusedBequest")
@Override
public void smoothScrollToPosition(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.State state, final int position) {
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
@Override
public int calculateDyToMakeVisible(final View view, final int snapPreference) {
if (!canScrollVertically()) {
return 0;
}
return getOffsetForCurrentView(view);
}
@Override
public int calculateDxToMakeVisible(final View view, final int snapPreference) {
if (!canScrollHorizontally()) {
return 0;
}
return getOffsetForCurrentView(view);
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
@Override
@Nullable
public PointF computeScrollVectorForPosition(final int targetPosition) {
if (0 == getChildCount()) {
return null;
}
final float directionDistance = getScrollDirection(targetPosition);
//noinspection NumericCastThatLosesPrecision
final int direction = (int) -Math.signum(directionDistance);
if (HORIZONTAL == mOrientation) {
return new PointF(direction, 0);
} else {
return new PointF(0, direction);
}
}
private float getScrollDirection(final int targetPosition) {
final float currentScrollPosition = makeScrollPositionInRange0ToCount(getCurrentScrollPosition(), mItemsCount);
if (mCircleLayout) {
final float t1 = currentScrollPosition - targetPosition;
final float t2 = Math.abs(t1) - mItemsCount;
if (Math.abs(t1) > Math.abs(t2)) {
return Math.signum(t1) * t2;
} else {
return t1;
}
} else {
return currentScrollPosition - targetPosition;
}
}
@Override
public int scrollVerticallyBy(final int dy, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
if (HORIZONTAL == mOrientation) {
return 0;
}
return scrollBy(dy, recycler, state);
}
@Override
public int scrollHorizontallyBy(final int dx, final RecyclerView.Recycler recycler, final RecyclerView.State state) {
if (VERTICAL == mOrientation) {
return 0;
}
return scrollBy(dx, recycler, state);
}
/**
* This method is called from {@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)} and
* {@link #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)} to calculate needed scroll that is allowed. <br />
* <br />
* This method may do relayout work.
*
* @param diff 要滚动的距离
* @param recycler 回收期
* @param state Transient state of RecyclerView
* @return distance that we actually scrolled by
*/
@CallSuper
protected int scrollBy(final int diff, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
if (null == mDecoratedChildWidth || null == mDecoratedChildHeight) {
return 0;
}
if (0 == getChildCount() || 0 == diff) {
return 0;
}
final int resultScroll;
if (mCircleLayout) {
resultScroll = diff;
mLayoutHelper.mScrollOffset += resultScroll;
final int maxOffset = getScrollItemSize() * mItemsCount;
while (0 > mLayoutHelper.mScrollOffset) {
mLayoutHelper.mScrollOffset += maxOffset;
}
while (mLayoutHelper.mScrollOffset > maxOffset) {
mLayoutHelper.mScrollOffset -= maxOffset;
}
mLayoutHelper.mScrollOffset -= resultScroll;
} else {
final int maxOffset = getMaxScrollOffset();
if (0 > mLayoutHelper.mScrollOffset + diff) {
resultScroll = -mLayoutHelper.mScrollOffset; //to make it 0
} else if (mLayoutHelper.mScrollOffset + diff > maxOffset) {
resultScroll = maxOffset - mLayoutHelper.mScrollOffset; //to make it maxOffset
} else {
resultScroll = diff;
}
}
if (0 != resultScroll) {
mLayoutHelper.mScrollOffset += resultScroll;
fillData(recycler, state);
}
return resultScroll;
}
@Override
public void onMeasure(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state, final int widthSpec, final int heightSpec) {
mDecoratedChildSizeInvalid = true;
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
@SuppressWarnings("rawtypes")
@Override
public void onAdapterChanged(final RecyclerView.Adapter oldAdapter, final RecyclerView.Adapter newAdapter) {
super.onAdapterChanged(oldAdapter, newAdapter);
removeAllViews();
}
@SuppressWarnings("RefusedBequest")
@Override
@CallSuper
public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
if (0 == state.getItemCount()) {
removeAndRecycleAllViews(recycler);
selectItemCenterPosition(INVALID_POSITION);
return;
}
detachAndScrapAttachedViews(recycler);
if (null == mDecoratedChildWidth || mDecoratedChildSizeInvalid) {
final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
final boolean shouldRecycle;
final View view;
if (scrapList.isEmpty()) {
shouldRecycle = true;
final int itemsCount = state.getItemCount();
view = recycler.getViewForPosition(
mPendingScrollPosition == INVALID_POSITION ?
0 :
Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition))
);
addView(view);
} else {
shouldRecycle = false;
view = scrapList.get(0).itemView;
}
measureChildWithMargins(view, 0, 0);
final int decoratedChildWidth = getDecoratedMeasuredWidth(view);
final int decoratedChildHeight = getDecoratedMeasuredHeight(view);
if (shouldRecycle) {
detachAndScrapView(view, recycler);
}
if (null != mDecoratedChildWidth && (mDecoratedChildWidth != decoratedChildWidth || mDecoratedChildHeight != decoratedChildHeight)) {
if (INVALID_POSITION == mPendingScrollPosition && null == mPendingCarouselSavedState) {
mPendingScrollPosition = mCenterItemPosition;
}
}
mDecoratedChildWidth = decoratedChildWidth;
mDecoratedChildHeight = decoratedChildHeight;
mDecoratedChildSizeInvalid = false;
}
if (INVALID_POSITION != mPendingScrollPosition) {
final int itemsCount = state.getItemCount();
mPendingScrollPosition = 0 == itemsCount ? INVALID_POSITION : Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition));
}
if (INVALID_POSITION != mPendingScrollPosition) {
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingScrollPosition, state);
mPendingScrollPosition = INVALID_POSITION;
mPendingCarouselSavedState = null;
} else if (null != mPendingCarouselSavedState) {
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingCarouselSavedState.mCenterItemPosition, state);
mPendingCarouselSavedState = null;
} else if (state.didStructureChange() && INVALID_POSITION != mCenterItemPosition) {
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mCenterItemPosition, state);
}
fillData(recycler, state);
}
private int calculateScrollForSelectingPosition(final int itemPosition, final RecyclerView.State state) {
if (itemPosition == INVALID_POSITION) {
return 0;
}
final int fixedItemPosition = itemPosition < state.getItemCount() ? itemPosition : state.getItemCount() - 1;
return fixedItemPosition * (VERTICAL == mOrientation ? mDecoratedChildHeight : mDecoratedChildWidth);
}
private void fillData(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
final float currentScrollPosition = getCurrentScrollPosition();
generateLayoutOrder(currentScrollPosition, state);
detachAndScrapAttachedViews(recycler);
recyclerOldViews(recycler);
final int width = getWidthNoPadding();
final int height = getHeightNoPadding();
if (VERTICAL == mOrientation) {
fillDataVertical(recycler, width, height);
} else {
fillDataHorizontal(recycler, width, height);
}
recycler.clear();
detectOnItemSelectionChanged(currentScrollPosition, state);
}
private void detectOnItemSelectionChanged(final float currentScrollPosition, final RecyclerView.State state) {
final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, state.getItemCount());
final int centerItem = Math.round(absCurrentScrollPosition);
if(currentScrollPosition-centerItem!=0){
new Handler(Looper.getMainLooper()).post(() -> dragDxDiff(currentScrollPosition,mCenterItemPosition));
}
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 dragDxDiff(final float centerItem,final int currentPosition) {
for (final OnDargAutoDiffListener onDargAutoDiffListener : onDargAutoDiffListeners) {
onDargAutoDiffListener.onDxChanged(centerItem,currentPosition);
}
}
private void fillDataVertical(final RecyclerView.Recycler recycler, final int width, final int height) {
final int start = (width - mDecoratedChildWidth) / 2;
final int end = start + mDecoratedChildWidth;
final int centerViewTop = (height - mDecoratedChildHeight) / 2;
for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff);
final int top = centerViewTop + offset;
final int bottom = top + mDecoratedChildHeight;
fillChildItem(start, top, end, bottom, layoutOrder, recycler, i);
}
}
private void fillDataHorizontal(final RecyclerView.Recycler recycler, final int width, final int height) {
final int top = (height - mDecoratedChildHeight) / 2;
final int bottom = top + mDecoratedChildHeight;
final int centerViewStart = (width - mDecoratedChildWidth) / 2;
for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff);
final int start = centerViewStart + offset;
final int end = start + mDecoratedChildWidth;
fillChildItem(start, top, end, bottom, layoutOrder, recycler, i);
}
}
@SuppressWarnings("MethodWithTooManyParameters")
private void fillChildItem(final int start, final int top, final int end, final int bottom, @NonNull final LayoutOrder layoutOrder, @NonNull final RecyclerView.Recycler recycler, final int i) {
final View view = bindChild(layoutOrder.mItemAdapterPosition, recycler);
ViewCompat.setElevation(view, i);
ItemTransformation transformation = null;
if (null != mViewPostLayout) {
transformation = mViewPostLayout.transformChild(view, layoutOrder.mItemPositionDiff, mOrientation, layoutOrder.mItemAdapterPosition);
}
if (null == transformation) {
view.layout(start, top, end, bottom);
} else {
view.layout(Math.round(start + transformation.mTranslationX), Math.round(top + transformation.mTranslationY),
Math.round(end + transformation.mTranslationX), Math.round(bottom + transformation.mTranslationY));
view.setScaleX(transformation.mScaleX);
view.setScaleY(transformation.mScaleY);
}
}
/**
* 中心项目的当前滚动位置。如果是循环布局,则该值可以在任何范围内。如果不是,那么它在[0-1]
*/
private float getCurrentScrollPosition() {
final int fullScrollSize = getMaxScrollOffset();
if (0 == fullScrollSize) {
return 0;
}
return 1.0f * mLayoutHelper.mScrollOffset / getScrollItemSize();
}
/**
* 填充布局中所有项目的最大滚动值。通常,这仅适用于非循环布局。
*/
private int getMaxScrollOffset() {
return getScrollItemSize() * (mItemsCount - 1);
}
/**
* Because we can support old Android versions, we should layout our children in specific order to make our center view in the top of layout
* (this item should layout last). So this method will calculate layout order and fill up {@link #mLayoutHelper} object.
* This object will be filled by only needed to layout items. Non visible items will not be there.
*
* @param currentScrollPosition current scroll position this is a value that indicates position of center item
* (if this value is int, then center item is really in the center of the layout, else it is near state).
* Be aware that this value can be in any range is it is cycle layout
* @param state Transient state of RecyclerView
* @see #getCurrentScrollPosition()
*/
private void generateLayoutOrder(final float currentScrollPosition, @NonNull final RecyclerView.State state) {
mItemsCount = state.getItemCount();
final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, mItemsCount);
final int centerItem = Math.round(absCurrentScrollPosition);
if (mCircleLayout && 1 < mItemsCount) {
final int layoutCount = Math.min(mLayoutHelper.mMaxVisibleItems * 2 + 1, mItemsCount);
mLayoutHelper.initLayoutOrder(layoutCount);
final int countLayoutHalf = layoutCount / 2;
// before center item
for (int i = 1; i <= countLayoutHalf; ++i) {
final int position = Math.round(absCurrentScrollPosition - i + mItemsCount) % mItemsCount;
mLayoutHelper.setLayoutOrder(countLayoutHalf - i, position, centerItem - absCurrentScrollPosition - i);
}
// after center item
for (int i = layoutCount - 1; i >= countLayoutHalf + 1; --i) {
final int position = Math.round(absCurrentScrollPosition - i + layoutCount) % mItemsCount;
mLayoutHelper.setLayoutOrder(i - 1, position, centerItem - absCurrentScrollPosition + layoutCount - i);
}
mLayoutHelper.setLayoutOrder(layoutCount - 1, centerItem, centerItem - absCurrentScrollPosition);
} else {
final int firstVisible = Math.max(centerItem - mLayoutHelper.mMaxVisibleItems, 0);
final int lastVisible = Math.min(centerItem + mLayoutHelper.mMaxVisibleItems, mItemsCount - 1);
final int layoutCount = lastVisible - firstVisible + 1;
mLayoutHelper.initLayoutOrder(layoutCount);
for (int i = firstVisible; i <= lastVisible; ++i) {
if (i == centerItem) {
mLayoutHelper.setLayoutOrder(layoutCount - 1, i, i - absCurrentScrollPosition);
} else if (i < centerItem) {
mLayoutHelper.setLayoutOrder(i - firstVisible, i, i - absCurrentScrollPosition);
} else {
mLayoutHelper.setLayoutOrder(layoutCount - (i - centerItem) - 1, i, i - absCurrentScrollPosition);
}
}
}
}
public int getWidthNoPadding() {
return getWidth() - getPaddingStart() - getPaddingEnd();
}
public int getHeightNoPadding() {
return getHeight() - getPaddingEnd() - getPaddingStart();
}
private View bindChild(final int position, @NonNull final RecyclerView.Recycler recycler) {
final View view = recycler.getViewForPosition(position);
addView(view);
measureChildWithMargins(view, 0, 0);
return view;
}
private void recyclerOldViews(final RecyclerView.Recycler recycler) {
for (RecyclerView.ViewHolder viewHolder : new ArrayList<>(recycler.getScrapList())) {
int adapterPosition = viewHolder.getAdapterPosition();
boolean found = false;
for (LayoutOrder layoutOrder : mLayoutHelper.mLayoutOrder) {
if (layoutOrder.mItemAdapterPosition == adapterPosition) {
found = true;
break;
}
}
if (!found) {
recycler.recycleView(viewHolder.itemView);
}
}
}
/**
* Called during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)} to calculate item offset from layout center line. <br />
* <br />
* Returns {@link #convertItemPositionDiffToSmoothPositionDiff(float)} * (size off area above center item when it is on the center). <br />
* Sign is: plus if this item is bellow center line, minus if not<br />
* <br />
* ----- - area above it<br />
* ||||| - center item<br />
* ----- - area bellow it (it has the same size as are above center item)<br />
*
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
* if this is 1 then this item is bellow the layout center line in the full item size distance.
* @return offset in scroll px coordinates.
*/
protected int getCardOffsetByPositionDiff(final float itemPositionDiff) {
final double smoothPosition = convertItemPositionDiffToSmoothPositionDiff(itemPositionDiff);
final int dimenDiff;
if (VERTICAL == mOrientation) {
dimenDiff = (getHeightNoPadding() - mDecoratedChildHeight) / 2;
} else {
dimenDiff = (getWidthNoPadding() - mDecoratedChildWidth) / 2;
}
//noinspection NumericCastThatLosesPrecision
return (int) Math.round(Math.signum(itemPositionDiff) * dimenDiff * smoothPosition);
}
/**
* Called during {@link #getCardOffsetByPositionDiff(float)} for better item movement. <br/>
* Current implementation speed up items that are far from layout center line and slow down items that are close to this line.
* This code is full of maths. If you want to make items move in a different way, probably you should override this method.<br />
* Please see code comments for better explanations.
*
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
* if this is 1 then this item is bellow the layout center line in the full item size distance.
* @return smooth position offset. needed for scroll calculation and better user experience.
* @see #getCardOffsetByPositionDiff(float)
*/
@SuppressWarnings({"MagicNumber", "InstanceMethodNamingConvention"})
protected double convertItemPositionDiffToSmoothPositionDiff(final float itemPositionDiff) {
// generally item moves the same way above center and bellow it. So we don't care about diff sign.
final float absIemPositionDiff = Math.abs(itemPositionDiff);
// we detect if this item is close for center or not. We use (1 / maxVisibleItem) ^ (1/3) as close definer.
if (absIemPositionDiff > StrictMath.pow(1.0f / mLayoutHelper.mMaxVisibleItems, 1.0f / 3)) {
// this item is far from center line, so we should make it move like square root function
return StrictMath.pow(absIemPositionDiff / mLayoutHelper.mMaxVisibleItems, 1 / 2.0f);
} else {
// this item is close from center line. we should slow it down and don't make it speed up very quick.
// so square function in range of [0, (1/maxVisible)^(1/3)] is quite good in it;
return StrictMath.pow(absIemPositionDiff, 2.0f);
}
}
/**
* @return full item size
*/
protected int getScrollItemSize() {
if (VERTICAL == mOrientation) {
return mDecoratedChildHeight;
} else {
return mDecoratedChildWidth;
}
}
@Override
public Parcelable onSaveInstanceState() {
if (null != mPendingCarouselSavedState) {
return new CarouselSavedState(mPendingCarouselSavedState);
}
final CarouselSavedState savedState = new CarouselSavedState(super.onSaveInstanceState());
savedState.mCenterItemPosition = mCenterItemPosition;
return savedState;
}
@Override
public void onRestoreInstanceState(final Parcelable state) {
if (state instanceof CarouselSavedState) {
mPendingCarouselSavedState = (CarouselSavedState) state;
super.onRestoreInstanceState(mPendingCarouselSavedState.mSuperState);
} else {
super.onRestoreInstanceState(state);
}
}
/**
* @return 从中心到最近项目的滚动偏移量
*/
protected int getOffsetCenterView() {
return Math.round(getCurrentScrollPosition()) * getScrollItemSize() - mLayoutHelper.mScrollOffset;
}
protected int getOffsetForCurrentView(@NonNull final View view) {
final int targetPosition = getPosition(view);
final float directionDistance = getScrollDirection(targetPosition);
return Math.round(directionDistance * getScrollItemSize());
}
/**
* 使滚动范围在[0count]内的Helper方法。通常只有循环布局才需要此方法。
*
* @param currentScrollPosition 滚动位置范围 个位数 view的index 小数滚动的范围
* @param count adapter 中的数量
* @return 在[0总数]范围内滚动位置良好
*/
private static float makeScrollPositionInRange0ToCount(final float currentScrollPosition, final int count) {
float absCurrentScrollPosition = currentScrollPosition;
while (0 > absCurrentScrollPosition) {
absCurrentScrollPosition += count;
}
while (Math.round(absCurrentScrollPosition) >= count) {
absCurrentScrollPosition -= count;
}
return absCurrentScrollPosition;
}
/**
* This interface methods will be called for each visible view item after general LayoutManager layout finishes. <br />
* <br />
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
*/
@SuppressWarnings("InterfaceNeverImplemented")
public abstract static class PostLayoutListener {
/**
* 子布局完成后调用。通常,您可以在这里进行任何平移和缩放工作。
*
* @param child view that was layout
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
* @param itemPositionInAdapter item position inside adapter for this layout pass
*/
public ItemTransformation transformChild(
@NonNull final View child,
final float itemPositionToCenterDiff,
final int orientation,
final int itemPositionInAdapter
) {
return transformChild(child, itemPositionToCenterDiff, orientation);
}
/**
* Called after child layout finished. Generally you can do any translation and scaling work here.
*
* @param child view that was layout
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
*/
public ItemTransformation transformChild(
@NonNull final View child,
final float itemPositionToCenterDiff,
final int orientation
) {
throw new IllegalStateException("at least one transformChild should be implemented");
}
}
public interface OnCenterItemSelectionListener {
/**
* Listener that will be called on every change of center item.
* This listener will be triggered on <b>every</b> layout operation if item was changed.
* Do not do any expensive operations in this method since this will effect scroll experience.
*
* @param adapterPosition current layout center item
*/
void onCenterItemChanged(final int adapterPosition);
}
public interface OnDargAutoDiffListener {
void onDxChanged(final float adapterPosition,final int currentPosition);
}
/**
* Helper class that holds currently visible items.
* Generally this class fills this list. <br />
* <br />
* This class holds all scroll and maxVisible items state.
*
* @see #getMaxVisibleItems()
*/
private static class LayoutHelper {
private int mMaxVisibleItems;
private int mScrollOffset;
private LayoutOrder[] mLayoutOrder;
private final List<WeakReference<LayoutOrder>> mReusedItems = new ArrayList<>();
LayoutHelper(final int maxVisibleItems) {
mMaxVisibleItems = maxVisibleItems;
}
/**
* Called before any fill calls. Needed to recycle old items and init new array list. Generally this list is an array an it is reused.
*
* @param layoutCount items count that will be layout
*/
void initLayoutOrder(final int layoutCount) {
if (null == mLayoutOrder || mLayoutOrder.length != layoutCount) {
if (null != mLayoutOrder) {
recycleItems(mLayoutOrder);
}
mLayoutOrder = new LayoutOrder[layoutCount];
fillLayoutOrder();
}
}
/**
* Called during layout generation process of filling this list. Should be called only after {@link #initLayoutOrder(int)} method call.
*
* @param arrayPosition position in layout order
* @param itemAdapterPosition adapter position of item for future data filling logic
* @param itemPositionDiff difference of current item scroll position and center item position.
* if this is a center item and it is in real center of layout, then this will be 0.
* if current layout is not in the center, then this value will never be int.
* if this item center is bellow layout center line then this value is greater then 0,
* else less then 0.
*/
void setLayoutOrder(final int arrayPosition, final int itemAdapterPosition, final float itemPositionDiff) {
final LayoutOrder item = mLayoutOrder[arrayPosition];
item.mItemAdapterPosition = itemAdapterPosition;
item.mItemPositionDiff = itemPositionDiff;
}
/**
* Checks is this screen Layout has this adapterPosition view in layout
*
* @param adapterPosition adapter position of item for future data filling logic
* @return true is adapterItem is in layout
*/
boolean hasAdapterPosition(final int adapterPosition) {
if (null != mLayoutOrder) {
for (final LayoutOrder layoutOrder : mLayoutOrder) {
if (layoutOrder.mItemAdapterPosition == adapterPosition) {
return true;
}
}
}
return false;
}
@SuppressWarnings("VariableArgumentMethod")
private void recycleItems(@NonNull final LayoutOrder... layoutOrders) {
for (final LayoutOrder layoutOrder : layoutOrders) {
//noinspection ObjectAllocationInLoop
mReusedItems.add(new WeakReference<>(layoutOrder));
}
}
private void fillLayoutOrder() {
for (int i = 0, length = mLayoutOrder.length; i < length; ++i) {
if (null == mLayoutOrder[i]) {
mLayoutOrder[i] = createLayoutOrder();
}
}
}
private LayoutOrder createLayoutOrder() {
final Iterator<WeakReference<LayoutOrder>> iterator = mReusedItems.iterator();
while (iterator.hasNext()) {
final WeakReference<LayoutOrder> layoutOrderWeakReference = iterator.next();
final LayoutOrder layoutOrder = layoutOrderWeakReference.get();
iterator.remove();
if (null != layoutOrder) {
return layoutOrder;
}
}
return new LayoutOrder();
}
}
/**
* Class that holds item data.
* This class is filled during {@link #generateLayoutOrder(float, RecyclerView.State)} and used during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)}
*/
private static class LayoutOrder {
/**
* Item adapter position
*/
private int mItemAdapterPosition;
/**
* Item center difference to layout center. If center of item is bellow layout center, then this value is greater then 0, else it is less.
*/
private float mItemPositionDiff;
}
protected static class CarouselSavedState implements Parcelable {
private final Parcelable mSuperState;
private int mCenterItemPosition;
protected CarouselSavedState(@Nullable final Parcelable superState) {
mSuperState = superState;
}
private CarouselSavedState(@NonNull final Parcel in) {
mSuperState = in.readParcelable(Parcelable.class.getClassLoader());
mCenterItemPosition = in.readInt();
}
protected CarouselSavedState(@NonNull final CarouselSavedState other) {
mSuperState = other.mSuperState;
mCenterItemPosition = other.mCenterItemPosition;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel parcel, final int i) {
parcel.writeParcelable(mSuperState, i);
parcel.writeInt(mCenterItemPosition);
}
public static final Creator<CarouselSavedState> CREATOR
= new Creator<CarouselSavedState>() {
@Override
public CarouselSavedState createFromParcel(final Parcel parcel) {
return new CarouselSavedState(parcel);
}
@Override
public CarouselSavedState[] newArray(final int i) {
return new CarouselSavedState[i];
}
};
}
}

View File

@@ -0,0 +1,47 @@
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
import android.view.View;
import androidx.annotation.NonNull;
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer;
/**
* Implementation of {@link CarouselLayoutManager.PostLayoutListener} that makes interesting scaling of items. <br />
* We are trying to make items scaling quicker for closer items for center and slower for when they are far away.<br />
* Tis implementation uses atan function for this purpose.
*/
public class CarouselZoomPostLayoutListener extends CarouselLayoutManager.PostLayoutListener {
private final float mScaleMultiplier;
public CarouselZoomPostLayoutListener() {
this(0.21f);
}
public CarouselZoomPostLayoutListener(final float scaleMultiplier) {
mScaleMultiplier = scaleMultiplier;
}
@Override
public ItemTransformation transformChild(@NonNull final View child, final float itemPositionToCenterDiff, final int orientation) {
float scale = 1.0f - mScaleMultiplier * Math.abs(itemPositionToCenterDiff);
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;
}
if(Math.abs(itemPositionToCenterDiff)==1){
if(child instanceof ConsultVideoPlayer){
((ConsultVideoPlayer)child).hideAllWidget();
}
}
return new ItemTransformation(scale, scale, translateX, translateY);
}
}

View File

@@ -0,0 +1,52 @@
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* Class for centering items after scroll event.<br />
* This class will listen to current scroll state and if item is not centered after scroll it will automatically scroll it to center.
*/
public class CenterScrollListener extends RecyclerView.OnScrollListener {
private boolean mAutoSet = true;
@Override
public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (!(layoutManager instanceof CarouselLayoutManager)) {
mAutoSet = true;
return;
}
final CarouselLayoutManager lm = (CarouselLayoutManager) layoutManager;
if (!mAutoSet) {
if (RecyclerView.SCROLL_STATE_IDLE == newState) {
final int scrollNeeded = lm.getOffsetCenterView();
// 滚动到中心位置
if (CarouselLayoutManager.HORIZONTAL == lm.getOrientation()) {
recyclerView.smoothScrollBy(scrollNeeded, 0);
} else {
recyclerView.smoothScrollBy(0, scrollNeeded);
}
pageSelect(recyclerView,newState);
mAutoSet = true;
}
}
if (RecyclerView.SCROLL_STATE_DRAGGING == newState || RecyclerView.SCROLL_STATE_SETTLING == newState) {
mAutoSet = false;
}
if(RecyclerView.SCROLL_STATE_DRAGGING == newState){
pageStop();
}
}
protected void pageStop() {
}
protected void pageSelect(RecyclerView recyclerView, final int newState) {
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,974 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mogo.och.taxi.passenger.utils.blur;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A cache that uses a bounded amount of space on a filesystem. Each cache
* entry has a string key and a fixed number of values. Each key must match
* the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
* accessible as streams or files. Each value must be between {@code 0} and
* {@code Integer.MAX_VALUE} bytes in length.
*
* <p>The cache stores its data in a directory on the filesystem. This
* directory must be exclusive to the cache; the cache may delete or overwrite
* files from its directory. It is an error for multiple processes to use the
* same cache directory at the same time.
*
* <p>This cache limits the number of bytes that it will store on the
* filesystem. When the number of stored bytes exceeds the limit, the cache will
* remove entries in the background until the limit is satisfied. The limit is
* not strict: the cache may temporarily exceed it while waiting for files to be
* deleted. The limit does not include filesystem overhead or the cache
* journal so space-sensitive applications should set a conservative limit.
*
* <p>Clients call {@link #edit} to create or update the values of an entry. An
* entry may have only one editor at one time; if a value is not available to be
* edited then {@link #edit} will return null.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to
* supply a full set of values; the empty value should be used as a
* placeholder if necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary
* to supply data for every value; values default to their previous
* value.
* </ul>
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
* of values as they were before or after the commit, but never a mix of values.
*
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
* observe the value at the time that {@link #get} was called. Updates and
* removals after the call do not impact ongoing reads.
*
* <p>This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If
* an error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
final class DiskLruCache implements Closeable {
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TEMP = "journal.tmp";
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
static final String MAGIC = "libcore.io.DiskLruCache";
static final String VERSION_1 = "1";
static final long ANY_SEQUENCE_NUMBER = -1;
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final File journalFileBackup;
private final int appVersion;
private long maxSize;
private int maxFileCount;
private final int valueCount;
private long size = 0;
private int fileCount = 0;
private Writer journalWriter;
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);
private int redundantOpCount;
/**
* To differentiate between old and current snapshots, each entry is given
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0;
/** This cache uses a single background thread to evict entries. */
final ThreadPoolExecutor executorService =
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private final Callable<Void> cleanupCallable = new Callable<Void>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
trimToFileCount();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
this.maxFileCount = maxFileCount;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @param maxFileCount the maximum file count this cache should store
* @throws IOException if reading or writing the cache directory fails
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (maxFileCount <= 0) {
throw new IllegalArgumentException("maxFileCount <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
cache.journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
return cache;
} catch (IOException journalIsCorrupt) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
cache.rebuildJournal();
return cache;
}
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ valueCountString + ", " + blank + "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
} finally {
Util.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
fileCount++;
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
/**
* Creates a new journal that omits redundant information. This replaces the
* current journal if it exists.
*/
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
Writer writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
try {
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY + ' ' + entry.key + '\n');
} else {
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
}
}
} finally {
writer.close();
}
if (journalFile.exists()) {
renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);
journalFileBackup.delete();
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
}
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
if (deleteDestination) {
deleteIfExists(to);
}
if (!from.renameTo(to)) {
throw new IOException();
}
}
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
File[] files = new File[valueCount];
InputStream[] ins = new InputStream[valueCount];
try {
File file;
for (int i = 0; i < valueCount; i++) {
file = entry.getCleanFile(i);
files[i] = file;
ins[i] = new FileInputStream(file);
}
} catch (FileNotFoundException e) {
// A file must have been deleted manually!
for (int i = 0; i < valueCount; i++) {
if (ins[i] != null) {
Util.closeQuietly(ins[i]);
} else {
break;
}
}
return null;
}
redundantOpCount++;
journalWriter.append(READ + ' ' + key + '\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;
}
/** Returns the directory where this cache stores its data. */
public File getDirectory() {
return directory;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public synchronized long getMaxSize() {
return maxSize;
}
/** Returns the maximum number of files that this cache should store */
public synchronized int getMaxFileCount() {
return maxFileCount;
}
/**
* Changes the maximum number of bytes the cache can store and queues a job
* to trim the existing store, if necessary.
*/
public synchronized void setMaxSize(long maxSize) {
this.maxSize = maxSize;
executorService.submit(cleanupCallable);
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public synchronized long size() {
return size;
}
/**
* Returns the number of files currently being used to store the values in
* this cache. This may be greater than the max file count if a background
* deletion is pending.
*/
public synchronized long fileCount() {
return fileCount;
}
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
// If this edit is creating the entry for the first time, every index must have a value.
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
}
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
fileCount++;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
}
journalWriter.flush();
if (size > maxSize || fileCount > maxFileCount || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
/**
* We only rebuild the journal when it will halve the size of the journal
* and eliminate at least 2000 ops.
*/
private boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold //
&& redundantOpCount >= lruEntries.size();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
fileCount--;
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/** Returns true if this cache has been closed. */
public synchronized boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException("cache is closed");
}
}
/** Force buffered operations to the filesystem. */
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
trimToFileCount();
journalWriter.flush();
}
/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
trimToFileCount();
journalWriter.close();
journalWriter = null;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
private void trimToFileCount() throws IOException {
while (fileCount > maxFileCount) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public void delete() throws IOException {
close();
Util.deleteContents(directory);
}
private void validateKey(String key) {
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
if (!matcher.matches()) {
throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
}
}
private static String inputStreamToString(InputStream in) throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
}
/** A snapshot of the values for an entry. */
public final class Snapshot implements Closeable {
private final String key;
private final long sequenceNumber;
private File[] files;
private final InputStream[] ins;
private final long[] lengths;
private Snapshot(String key, long sequenceNumber, File[] files, InputStream[] ins, long[] lengths) {
this.key = key;
this.sequenceNumber = sequenceNumber;
this.files = files;
this.ins = ins;
this.lengths = lengths;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public Editor edit() throws IOException {
return DiskLruCache.this.edit(key, sequenceNumber);
}
/** Returns file with the value for {@code index}. */
public File getFile(int index) {
return files[index];
}
/** Returns the unbuffered stream with the value for {@code index}. */
public InputStream getInputStream(int index) {
return ins[index];
}
/** Returns the string value for {@code index}. */
public String getString(int index) throws IOException {
return inputStreamToString(getInputStream(index));
}
/** Returns the byte length of the value for {@code index}. */
public long getLength(int index) {
return lengths[index];
}
public void close() {
for (InputStream in : ins) {
Util.closeQuietly(in);
}
}
}
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
@Override
public void write(int b) throws IOException {
// Eat all writes silently. Nom nom.
}
};
/** Edits the values for an entry. */
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public InputStream newInputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
return null;
}
try {
return new FileInputStream(entry.getCleanFile(index));
} catch (FileNotFoundException e) {
return null;
}
}
}
/**
* Returns the last committed value as a string, or null if no value
* has been committed.
*/
public String getString(int index) throws IOException {
InputStream in = newInputStream(index);
return in != null ? inputStreamToString(in) : null;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public OutputStream newOutputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e) {
// Attempt to recreate the cache directory.
directory.mkdirs();
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e2) {
// We are unable to recover. Silently eat the writes.
return NULL_OUTPUT_STREAM;
}
}
return new FaultHidingOutputStream(outputStream);
}
}
/** Sets the value at {@code index} to {@code value}. */
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
writer.write(value);
} finally {
Util.closeQuietly(writer);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {
completeEdit(this, false);
}
public void abortUnlessCommitted() {
if (!committed) {
try {
abort();
} catch (IOException ignored) {
}
}
}
private class FaultHidingOutputStream extends FilterOutputStream {
private FaultHidingOutputStream(OutputStream out) {
super(out);
}
@Override public void write(int oneByte) {
try {
out.write(oneByte);
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void write(byte[] buffer, int offset, int length) {
try {
out.write(buffer, offset, length);
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void close() {
try {
out.close();
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void flush() {
try {
out.flush();
} catch (IOException e) {
hasErrors = true;
}
}
}
}
private final class Entry {
private final String key;
/** Lengths of this entry's files. */
private final long[] lengths;
/** True if this entry has ever been published. */
private boolean readable;
/** The ongoing edit or null if this entry is not being edited. */
private Editor currentEditor;
/** The sequence number of the most recently committed edit to this entry. */
private long sequenceNumber;
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
}
public String getLengths() throws IOException {
StringBuilder result = new StringBuilder();
for (long size : lengths) {
result.append(' ').append(size);
}
return result.toString();
}
/** Set lengths using decimal numbers like "10123". */
private void setLengths(String[] strings) throws IOException {
if (strings.length != valueCount) {
throw invalidLengths(strings);
}
try {
for (int i = 0; i < strings.length; i++) {
lengths[i] = Long.parseLong(strings[i]);
}
} catch (NumberFormatException e) {
throw invalidLengths(strings);
}
}
private IOException invalidLengths(String[] strings) throws IOException {
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
}
public File getCleanFile(int i) {
return new File(directory, key + "" + i);
}
public File getDirtyFile(int i) {
return new File(directory, key + "" + i + ".tmp");
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,241 @@
package com.mogo.och.taxi.passenger.utils.blur;
import android.graphics.Bitmap;
/**
* Created by jay on 11/7/15.
*/
public class FastBlurUtil {
public static Bitmap doBlur(Bitmap sentBitmap, int scaleRadius, int radius) {
// Stack Blur v1.0 from
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
//
// Java Author: Mario Klingemann <mario at quasimondo.com>
// http://incubator.quasimondo.com
// created Feburary 29, 2004
// Android port : Yahel Bouaziz <yahel at kayenko.com>
// http://www.kayenko.com
// ported april 5th, 2012
// This is a compromise between Gaussian Blur and Box blur
// It creates much better looking blurs than Box Blur, but is
// 7x faster than my Gaussian Blur implementation.
//
// I called it Stack Blur because this describes best how this
// filter works internally: it creates a kind of moving stack
// of colors whilst scanning through the image. Thereby it
// just has to add one new block of color to the right side
// of the stack and remove the leftmost color. The remaining
// colors on the topmost layer of the stack are either added on
// or reduced by one, depending on if they are on the right or
// on the left side of the stack.
//
// If you are using this algorithm in your code please add
// the following line:
//
// Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
if (scaleRadius > 0) {
sentBitmap = Bitmap.createScaledBitmap(sentBitmap, sentBitmap.getWidth() / scaleRadius,
sentBitmap.getHeight() / scaleRadius, false);
}
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
}

View File

@@ -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 ) {
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mogo.och.taxi.passenger.utils.blur;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
/**
* Buffers input from an {@link InputStream} for reading lines.
*
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
* line at end of input is invalid and will be ignored, the caller may use {@code
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
*
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
* end-of-input reporting and a more restrictive definition of a line.
*
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
* and 10, respectively, and the representation of no other character contains these values.
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
* The default charset is US_ASCII.
*/
class StrictLineReader implements Closeable {
private static final byte CR = (byte) '\r';
private static final byte LF = (byte) '\n';
private final InputStream in;
private final Charset charset;
/*
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
*/
private byte[] buf;
private int pos;
private int end;
/**
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
*
* @param in the {@code InputStream} to read data from.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if the specified charset is not supported.
*/
public StrictLineReader(InputStream in, Charset charset) {
this(in, 8192, charset);
}
/**
* Constructs a new {@code LineReader} with the specified capacity and charset.
*
* @param in the {@code InputStream} to read data from.
* @param capacity the capacity of the buffer.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if {@code capacity} is negative or zero
* or the specified charset is not supported.
*/
public StrictLineReader(InputStream in, int capacity, Charset charset) {
if (in == null || charset == null) {
throw new NullPointerException();
}
if (capacity < 0) {
throw new IllegalArgumentException("capacity <= 0");
}
if (!(charset.equals(Util.US_ASCII))) {
throw new IllegalArgumentException("Unsupported encoding");
}
this.in = in;
this.charset = charset;
buf = new byte[capacity];
}
/**
* Closes the reader by closing the underlying {@code InputStream} and
* marking this reader as closed.
*
* @throws IOException for errors when closing the underlying {@code InputStream}.
*/
public void close() throws IOException {
synchronized (in) {
if (buf != null) {
buf = null;
in.close();
}
}
}
/**
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
* this end of line marker is not included in the result.
*
* @return the next line from the input.
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
*/
public String readLine() throws IOException {
synchronized (in) {
if (buf == null) {
throw new IOException("LineReader is closed");
}
// Read more data if we are at the end of the buffered data.
// Though it's an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
if (pos >= end) {
fillBuf();
}
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
String res = new String(buf, pos, lineEnd - pos, charset.name());
pos = i + 1;
return res;
}
}
// Let's anticipate up to 80 characters on top of those already read.
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
@Override
public String toString() {
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
try {
return new String(buf, 0, length, charset.name());
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // Since we control the charset this will never happen.
}
}
};
while (true) {
out.write(buf, pos, end - pos);
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end = -1;
fillBuf();
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
if (i != pos) {
out.write(buf, pos, i - pos);
}
pos = i + 1;
return out.toString();
}
}
}
}
}
/**
* Reads new input data into the buffer. Call only with pos == end or end == -1,
* depending on the desired outcome if the function throws.
*/
private void fillBuf() throws IOException {
int result = in.read(buf, 0, buf.length);
if (result == -1) {
throw new EOFException();
}
pos = 0;
end = result;
}
}

View File

@@ -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) {
}
}
}
}

View File

@@ -0,0 +1,55 @@
package com.mogo.och.taxi.passenger.utils.windowdispatch;
import android.graphics.Region;
import android.inputmethodservice.InputMethodService;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class OnComputeInternalInsetsListener implements InvocationHandler {
private Region touchRegion = null;
public Object getListener() {
Object target = null;
try {
Class class1 = Class.forName("android.view.ViewTreeObserver$OnComputeInternalInsetsListener");
target = Proxy.newProxyInstance(OnComputeInternalInsetsListener.class.getClassLoader(),
new Class[]{class1}, this);
} catch (Exception e) {
e.printStackTrace();
}
return target;
}
public Region getTouchRegion() {
return touchRegion;
}
public void setTouchRegion(Region touchRegion) {
this.touchRegion = touchRegion;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
Field regionField = args[0].getClass()
.getDeclaredField("touchableRegion");
regionField.setAccessible(true);
Field insetField = args[0].getClass()
.getDeclaredField("mTouchableInsets");
insetField.setAccessible(true);
if (touchRegion != null) {
Region region = (Region) regionField.get(args[0]);
region.set(touchRegion);
insetField.set(args[0], InputMethodService.Insets.TOUCHABLE_INSETS_REGION);
} else {
insetField.set(args[0], InputMethodService.Insets.TOUCHABLE_INSETS_FRAME);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,51 @@
package com.mogo.och.taxi.passenger.utils.windowdispatch;
import android.view.ViewTreeObserver;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectionUtils {
private ReflectionUtils() {
}
public static void removeOnComputeInternalInsetsListener(ViewTreeObserver viewTree) {
if (viewTree == null) {
return;
}
try {
Class<?> clazz = Class.forName("android.view.ViewTreeObserver");
Field field = viewTree.getClass().getDeclaredField("mOnComputeInternalInsetsListeners");
field.setAccessible(true);
Object listenerList = field.get(viewTree);
Method method = listenerList.getClass().getDeclaredMethod("getArray");
method.setAccessible(true);
ArrayList<Object> list = (ArrayList<Object>) method.invoke(listenerList);
Class<?> classes[] = {Class.forName("android.view.ViewTreeObserver$OnComputeInternalInsetsListener")};
if (list != null && list.size() > 0) {
clazz.getDeclaredMethod("removeOnComputeInternalInsetsListener", classes).invoke(viewTree,
list.get(0));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void addOnComputeInternalInsetsListener(ViewTreeObserver viewTree, Object object) {
if (viewTree == null) {
return;
}
try {
Class<?> classes[] = {Class.forName("android.view.ViewTreeObserver$OnComputeInternalInsetsListener")};
Class<?> clazz = Class.forName("android.view.ViewTreeObserver");
clazz.getDeclaredMethod("addOnComputeInternalInsetsListener", classes).invoke(viewTree,
object);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,391 @@
package com.mogo.och.taxi.passenger.widget
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.*
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.constraintlayout.widget.ConstraintLayout
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.ui.video.FullVideoUtils
import com.shuyu.gsyvideoplayer.listener.VideoAllCallBack
import com.shuyu.gsyvideoplayer.utils.Debuger
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import java.lang.reflect.Constructor
/**
* @author lixiaopeng
* @since 2021/11/3
*
* 视频播放器ui定制
*/
class ConsultVideoPlayer : StandardGSYVideoPlayer {
private lateinit var start: ImageView
lateinit var coverImage: ImageView
private lateinit var currentTimeTextView: TextView
private lateinit var totalTimeTextView: TextView
private lateinit var aivStartPlay: AppCompatImageView
private lateinit var tvTitle: TextView
private lateinit var layoutBottom: ConstraintLayout
private var fullVideoPlayer:ConsultVideoPlayer?=null
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, fullFlag: Boolean?) : super(context, fullFlag)
override fun init(context: Context) {
mEnlargeImageRes = R.drawable.taxi_p_change_full
super.init(context)
start = findViewById(R.id.start)
coverImage = findViewById(R.id.thumbImage)
currentTimeTextView = findViewById(R.id.current)
totalTimeTextView = findViewById(R.id.total)
aivStartPlay = findViewById(R.id.aiv_start_play)
layoutBottom = findViewById(R.id.layout_bottom)
tvTitle = findViewById(R.id.tv_title)
fullscreenButton.setOnClickListener(this)
aivStartPlay.setOnClickListener(this)
if (mThumbImageViewLayout != null
&& (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)
) {
mThumbImageViewLayout.visibility = View.VISIBLE
}
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_16_9)
}
override fun getLayoutId(): Int {
return R.layout.taxi_p_video_show
}
fun setTitle(title:String){
tvTitle.text = title
}
override fun updateStartImage() {
when (mCurrentState) {
GSYVideoView.CURRENT_STATE_PLAYING ->{
start.setImageResource(R.drawable.notice_video_pause)
aivStartPlay.visibility = View.GONE
}
GSYVideoView.CURRENT_STATE_ERROR ->{
start.setImageResource(R.drawable.notice_video_pause)
aivStartPlay.visibility = View.GONE
}
else -> {
start.setImageResource(R.drawable.notice_video_after_pause)
aivStartPlay.visibility = View.VISIBLE
}
}
}
override fun touchDoubleUp() {
}
public override fun hideAllWidget() {
super.hideAllWidget()
}
override fun setProgressAndTime(
progress: Int,
secProgress: Int,
currentTime: Int,
totalTime: Int,
forceChange: Boolean
) {
super.setProgressAndTime(progress, secProgress, currentTime, totalTime, forceChange)
//时间显示
currentTimeTextView.text = TimeTransformUtils.stringForTime(currentTime)
totalTimeTextView.text = TimeTransformUtils.stringForTime(totalTime)
if (progress != 0) {
mProgressBar?.progress = progress
}
}
override fun showWifiDialog() {
//直接播放不显示WIFI对话框
startPlayLogic()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mProgressBar?.progress = 0
mFullPauseBitmap = null
if(mIfCurrentIsFullscreen){
FullVideoUtils.dismissOverlayView()
fullVideoPlayer?.let {
clearFullscreenLayout(it)
}
fullVideoPlayer?.onVideoReset()
thumbImageViewLayout.visibility = View.VISIBLE
}else{
onVideoReset()
}
}
override fun onClick(v: View?) {
super.onClick(v)
when (v?.id) {
R.id.fullscreen -> {
startWindowFullscreenOwn(context, false, false)
}
R.id.aiv_start_play -> {
if(currentState==GSYVideoView.CURRENT_STATE_PAUSE){
onVideoResume(false)
}else{
startPlayLogic()
}
}
else -> {}
}
}
override fun onCompletion() {
start.setImageResource(R.drawable.notice_video_after_pause)
}
override fun onSurfaceUpdated(surface: Surface) {
super.onSurfaceUpdated(surface)
if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
mThumbImageViewLayout.visibility = View.INVISIBLE
}
}
override fun onPrepared() {
super.onPrepared()
}
override fun onBufferingUpdate(percent: Int) {
super.onBufferingUpdate(percent)
}
override fun onError(what: Int, extra: Int) {
super.onError(what, extra)
}
override fun setViewShowState(view: View?, visibility: Int) {
if (view === mThumbImageViewLayout && visibility != View.VISIBLE) {
return
}
super.setViewShowState(view, visibility)
}
override fun onSurfaceAvailable(surface: Surface) {
super.onSurfaceAvailable(surface)
if (GSYVideoType.getRenderType() != GSYVideoType.TEXTURE) {
// if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
// mThumbImageViewLayout.visibility = View.INVISIBLE
// }
}
}
override fun onAutoCompletion() {
super.onAutoCompletion()
if(mIfCurrentIsFullscreen){
FullVideoUtils.dismissOverlayView()
fullVideoPlayer?.let {
clearFullscreenLayout(it)
}
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (!mIfCurrentIsFullscreen) {
this.outlineProvider = TextureVideoViewOutlineProvider(40F)
this.clipToOutline = true
}
}
private fun startWindowFullscreenOwn(context:Context, actionBar:Boolean, statusBar:Boolean){
val gsyBaseVideoPlayer = startWindowFullscreen(context, actionBar, statusBar)
gsyBaseVideoPlayer?.let {
val gsyVideoPlayer = it as StandardGSYVideoPlayer
gsyVideoPlayer.setLockClickListener(mLockClickListener)
gsyVideoPlayer.isNeedLockFull = isNeedLockFull
initFullUI(gsyVideoPlayer)
}
}
private fun initFullUI(standardGSYVideoPlayer: StandardGSYVideoPlayer) {
if (mBottomProgressDrawable != null) {
standardGSYVideoPlayer.setBottomProgressBarDrawable(mBottomProgressDrawable)
}
if (mBottomShowProgressDrawable != null && mBottomShowProgressThumbDrawable != null) {
standardGSYVideoPlayer.setBottomShowProgressBarDrawable(
mBottomShowProgressDrawable,
mBottomShowProgressThumbDrawable
)
}
if (mVolumeProgressDrawable != null) {
standardGSYVideoPlayer.setDialogVolumeProgressBar(mVolumeProgressDrawable)
}
if (mDialogProgressBarDrawable != null) {
standardGSYVideoPlayer.setDialogProgressBar(mDialogProgressBarDrawable)
}
if (mDialogProgressHighLightColor >= 0 && mDialogProgressNormalColor >= 0) {
standardGSYVideoPlayer.setDialogProgressColor(
mDialogProgressHighLightColor,
mDialogProgressNormalColor
)
}
}
override fun startWindowFullscreen(context:Context, actionBar:Boolean, statusBar:Boolean):GSYBaseVideoPlayer?{
if (mTextureViewContainer.childCount > 0) {
mTextureViewContainer.removeAllViews()
}
var hadNewConstructor = true
//切换时关闭非全屏定时器
cancelProgressTimer()
try {
this@ConsultVideoPlayer.javaClass.getConstructor(
Context::class.java,
Boolean::class.java
)
} catch (e: java.lang.Exception) {
hadNewConstructor = false
}
try {
//通过被重载的不同构造器来选择
val constructor: Constructor<ConsultVideoPlayer>
val gsyVideoPlayer: ConsultVideoPlayer
if (!hadNewConstructor) {
constructor = this@ConsultVideoPlayer.javaClass.getConstructor(Context::class.java)
gsyVideoPlayer = constructor.newInstance(mContext)
} else {
constructor = this@ConsultVideoPlayer.javaClass.getConstructor(
Context::class.java,
Boolean::class.java
)
gsyVideoPlayer = constructor.newInstance(mContext, true)
}
this.fullVideoPlayer = gsyVideoPlayer
gsyVideoPlayer.id = fullId
gsyVideoPlayer.isIfCurrentIsFullscreen = true
gsyVideoPlayer.setVideoAllCallBack(mVideoAllCallBack)
cloneParams(this, gsyVideoPlayer)
val frameLayout = FrameLayout(context)
if (gsyVideoPlayer.fullscreenButton != null) {
gsyVideoPlayer.fullscreenButton.setImageResource(R.drawable.taxi_p_change_normal)
gsyVideoPlayer.fullscreenButton.setOnClickListener { v ->
if (mBackFromFullScreenListener == null) {
FullVideoUtils.dismissOverlayView()
clearFullscreenLayout(gsyVideoPlayer)
} else {
mBackFromFullScreenListener.onClick(v)
}
}
}
frameLayout.setBackgroundColor(Color.BLACK)
val lp = LayoutParams(
width, height
)
frameLayout.addView(gsyVideoPlayer, lp)
FullVideoUtils.showOverlayView(context as Activity,frameLayout,R.style.och_window_anim_alpha)
gsyVideoPlayer.visibility = INVISIBLE
frameLayout.visibility = INVISIBLE
resolveFullVideoShow(context, gsyVideoPlayer, frameLayout)
gsyVideoPlayer.addTextureView()
gsyVideoPlayer.startProgressTimer()
gsyVideoManager.setLastListener(this)
gsyVideoManager.setListener(gsyVideoPlayer)
checkoutState()
thumbImageViewLayout.visibility = View.VISIBLE
return gsyVideoPlayer
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return null
}
/**
* 全屏
*/
override fun resolveFullVideoShow(
context: Context?,
gsyVideoPlayer: GSYBaseVideoPlayer,
frameLayout: FrameLayout
) {
val lp = gsyVideoPlayer.layoutParams as LayoutParams
lp.setMargins(0, 0, 0, 0)
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.gravity = Gravity.CENTER
gsyVideoPlayer.layoutParams = lp
gsyVideoPlayer.isIfCurrentIsFullscreen = true
val isVertical = isVerticalFullByVideoSize
val isLockLand = isLockLandByAutoFullSize
if (isShowFullAnimation) {
mInnerHandler.postDelayed({ //autoFull模式下非横屏视频视频不横屏并且不自动旋转
if (!isVertical && isLockLand && mOrientationUtils != null && mOrientationUtils.isLand != 1) {
mOrientationUtils.resolveByClick()
}
gsyVideoPlayer.visibility = VISIBLE
frameLayout.visibility = VISIBLE
}, 300)
} else {
if (!isVertical && isLockLand && mOrientationUtils != null) {
mOrientationUtils.resolveByClick()
}
gsyVideoPlayer.visibility = VISIBLE
frameLayout.visibility = VISIBLE
}
if (mVideoAllCallBack != null) {
Debuger.printfError("onEnterFullscreen")
mVideoAllCallBack.onEnterFullscreen(mOriginUrl, mTitle, gsyVideoPlayer)
}
mIfCurrentIsFullscreen = true
checkoutState()
checkAutoFullWithSizeAndAdaptation(gsyVideoPlayer)
}
fun clearFullscreenLayout(gsyVideoPlayer:GSYVideoPlayer) {
mIfCurrentIsFullscreen = false
val delay = 0
mInnerHandler.postDelayed({ resolveNormalVideoShow(gsyVideoPlayer) }, delay.toLong())
}
private fun resolveNormalVideoShow(gsyVideoPlayer: GSYVideoPlayer) {
mCurrentState = gsyVideoManager.lastState
cloneParams(gsyVideoPlayer, this)
gsyVideoManager.setListener(gsyVideoManager.lastListener())
gsyVideoManager.setLastListener(null)
setStateAndUi(mCurrentState)
addTextureView()
mSaveChangeViewTIme = System.currentTimeMillis()
if (mVideoAllCallBack != null) {
Debuger.printfError("onQuitFullscreen")
mVideoAllCallBack.onQuitFullscreen(mOriginUrl, mTitle, this)
}
mIfCurrentIsFullscreen = false
if (fullscreenButton != null) {
fullscreenButton.setImageResource(enlargeImageRes)
}
this.fullVideoPlayer = null
}
fun getVideoAllCallBack(): VideoAllCallBack? {
return mVideoAllCallBack
}
}

View File

@@ -0,0 +1,39 @@
package com.mogo.och.taxi.passenger.widget;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
public class ResizeAnimation extends Animation {
final int targetHeight;
View view;
int startHeight;
public ResizeAnimation(View view, int targetHeight, int startHeight) {
this.view = view;
this.targetHeight = targetHeight;
this.startHeight = startHeight;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight = (int) (startHeight + (targetHeight-startHeight) * interpolatedTime);
view.getLayoutParams().height = newHeight;
view.requestLayout();
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
@Override
public boolean willChangeBounds() {
return true;
}
}

View File

@@ -0,0 +1,68 @@
package com.mogo.och.taxi.passenger.widget.indicator
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorOrientation
import com.zhpan.indicator.base.BaseIndicatorView
import com.mogo.och.taxi.passenger.widget.indicator.drawer.DrawerProxy
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.option.AttrsController
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
class IndicatorView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseIndicatorView(context, attrs, defStyleAttr) {
private var mDrawerProxy: DrawerProxy
init {
AttrsController.initAttrs(context, attrs, mIndicatorOptions)
mDrawerProxy = DrawerProxy(mIndicatorOptions)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
mDrawerProxy.onLayout(changed, left, top, right, bottom)
}
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val measureResult = mDrawerProxy.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measureResult.measureWidth, measureResult.measureHeight)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
rotateCanvas(canvas)
mDrawerProxy.onDraw(canvas)
}
override fun setIndicatorOptions(options: IndicatorOptions) {
super.setIndicatorOptions(options)
mDrawerProxy.setIndicatorOptions(options)
}
override fun notifyDataChanged(itemCount:Int) {
mDrawerProxy = DrawerProxy(mIndicatorOptions)
super.notifyDataChanged(itemCount)
}
private fun rotateCanvas(canvas: Canvas) {
if (mIndicatorOptions.orientation == IndicatorOrientation.INDICATOR_VERTICAL) {
canvas.rotate(90f, width / 2f, width / 2f)
} else if (mIndicatorOptions.orientation == IndicatorOrientation.INDICATOR_RTL) {
canvas.rotate(180f, width / 2f, height / 2f)
}
}
fun setOrientation(@AIndicatorOrientation orientation: Int) {
mIndicatorOptions.orientation = orientation;
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.och.taxi.passenger.widget.indicator.annotation
import androidx.annotation.IntDef
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
/**
*
* @author zhangpan
* @date 2021/1/21
*/
@IntDef(
IndicatorOrientation.INDICATOR_HORIZONTAL, IndicatorOrientation.INDICATOR_VERTICAL,
IndicatorOrientation.INDICATOR_RTL
)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD)
annotation class AIndicatorOrientation()

View File

@@ -0,0 +1,19 @@
package com.mogo.och.taxi.passenger.widget.indicator.annotation
import androidx.annotation.IntDef
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.COLOR
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.NORMAL
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.SCALE
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.SMOOTH
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.WORM
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
@IntDef(NORMAL, SMOOTH, WORM, COLOR, SCALE)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD)
annotation class AIndicatorSlideMode

View File

@@ -0,0 +1,15 @@
package com.mogo.och.taxi.passenger.widget.indicator.annotation
import androidx.annotation.IntDef
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorStyle
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
@IntDef(IndicatorStyle.CIRCLE, IndicatorStyle.DASH, IndicatorStyle.ROUND_RECT)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD)
annotation class AIndicatorStyle

View File

@@ -0,0 +1,207 @@
package com.zhpan.indicator.base
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorStyle
import com.mogo.och.taxi.passenger.widget.indicator.base.IIndicator
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhangpan on 2019-09-04.
* Description:IndicatorView基类处理了页面滑动。
* </pre>
*/
@Suppress("UNUSED")
open class BaseIndicatorView constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int
) : View(context, attrs, defStyleAttr), IIndicator {
var mIndicatorOptions: IndicatorOptions
private var recyclerView: RecyclerView? = null
init {
mIndicatorOptions = IndicatorOptions()
}
fun setPageSize(pageSize: Int): BaseIndicatorView {
mIndicatorOptions.pageSize = pageSize
return this
}
// 页面选定
fun onPageSelected(position: Int) {
System.err.println("BaseIndicatorView---onPageSelected:" + position)
if (getSlideMode() == IndicatorSlideMode.NORMAL) {
setCurrentPosition(position)
setSlideProgress(0f)
invalidate()
}
}
// 滚动距离
fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
if (getSlideMode() != IndicatorSlideMode.NORMAL && getPageSize() > 1) {
scrollSlider(position, positionOffset)
invalidate()
}
}
private fun scrollSlider(position: Int, positionOffset: Float) {
if (mIndicatorOptions.slideMode == IndicatorSlideMode.SCALE
|| mIndicatorOptions.slideMode == IndicatorSlideMode.COLOR) {
setCurrentPosition(position)
setSlideProgress(positionOffset)
} else {
if (position % getPageSize() == getPageSize() - 1) { // 最后一个页面与第一个页面
if (positionOffset < 0.5) {
setCurrentPosition(position)
setSlideProgress(0f)
} else {
setCurrentPosition(0)
setSlideProgress(0f)
}
} else { // 中间页面
setCurrentPosition(position)
setSlideProgress(positionOffset)
}
}
}
override fun notifyDataChanged(itemCount: Int) {
setPageSize(itemCount)
requestLayout()
invalidate()
}
private fun setupViewPager() {
}
fun getNormalSlideWidth(): Float {
return mIndicatorOptions.normalSliderWidth
}
fun setNormalSlideWidth(normalSliderWidth: Float) {
mIndicatorOptions.normalSliderWidth = normalSliderWidth
}
fun getCheckedSlideWidth(): Float {
return mIndicatorOptions.checkedSliderWidth
}
fun setCheckedSlideWidth(checkedSliderWidth: Float) {
mIndicatorOptions.checkedSliderWidth = checkedSliderWidth
}
val checkedSliderWidth: Float
get() = mIndicatorOptions.checkedSliderWidth
fun setCurrentPosition(currentPosition: Int) {
mIndicatorOptions.currentPosition = currentPosition
}
fun getCurrentPosition(): Int {
return mIndicatorOptions.currentPosition
}
fun getIndicatorGap(indicatorGap: Float) {
mIndicatorOptions.sliderGap = indicatorGap
}
fun setIndicatorGap(indicatorGap: Float) {
mIndicatorOptions.sliderGap = indicatorGap
}
fun setCheckedColor(@ColorInt normalColor: Int) {
mIndicatorOptions.checkedSliderColor = normalColor
}
fun getCheckedColor(): Int {
return mIndicatorOptions.checkedSliderColor
}
fun setNormalColor(@ColorInt normalColor: Int) {
mIndicatorOptions.normalSliderColor = normalColor
}
fun getSlideProgress(): Float {
return mIndicatorOptions.slideProgress
}
fun setSlideProgress(slideProgress: Float) {
mIndicatorOptions.slideProgress = slideProgress
}
fun getPageSize(): Int {
return mIndicatorOptions.pageSize
}
fun setSliderColor(
@ColorInt normalColor: Int,
@ColorInt selectedColor: Int
): BaseIndicatorView {
mIndicatorOptions.setSliderColor(normalColor, selectedColor)
return this
}
fun setSliderWidth(sliderWidth: Float): BaseIndicatorView {
mIndicatorOptions.setSliderWidth(sliderWidth)
return this
}
fun setSliderWidth(
normalSliderWidth: Float,
selectedSliderWidth: Float
): BaseIndicatorView {
mIndicatorOptions.setSliderWidth(normalSliderWidth, selectedSliderWidth)
return this
}
fun setSliderGap(sliderGap: Float): BaseIndicatorView {
mIndicatorOptions.sliderGap = sliderGap
return this
}
fun getSlideMode(): Int {
return mIndicatorOptions.slideMode
}
fun setSlideMode(@AIndicatorSlideMode slideMode: Int): BaseIndicatorView {
mIndicatorOptions.slideMode = slideMode
return this
}
fun setIndicatorStyle(@AIndicatorStyle indicatorStyle: Int): BaseIndicatorView {
mIndicatorOptions.indicatorStyle = indicatorStyle
return this
}
fun setSliderHeight(sliderHeight: Float): BaseIndicatorView {
mIndicatorOptions.sliderHeight = sliderHeight
return this
}
fun showIndicatorWhenOneItem(showIndicatorWhenOneItem: Boolean) {
mIndicatorOptions.showIndicatorOneItem = showIndicatorWhenOneItem
}
fun onPageScrollStateChanged(state: Int) {
}
override fun setIndicatorOptions(options: IndicatorOptions) {
mIndicatorOptions = options
}
}

View File

@@ -0,0 +1,18 @@
package com.mogo.och.taxi.passenger.widget.indicator.base
import androidx.viewpager.widget.ViewPager
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhangpan on 2019-09-02.
* Description:
</pre> *
*/
interface IIndicator {
fun notifyDataChanged(itemCount:Int)
fun setIndicatorOptions(options: IndicatorOptions)
}

View File

@@ -0,0 +1,94 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.animation.ArgbEvaluator
import android.graphics.Paint
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description:
</pre> *
*/
abstract class BaseDrawer internal constructor(internal var mIndicatorOptions: IndicatorOptions) :
IDrawer {
private val mMeasureResult: MeasureResult
internal var maxWidth: Float = 0.toFloat()
internal var minWidth: Float = 0.toFloat()
internal var mPaint: Paint = Paint()
internal var argbEvaluator: ArgbEvaluator? = null
companion object {
const val INDICATOR_PADDING_ADDITION = 6
const val INDICATOR_PADDING = 3
}
protected val isWidthEquals: Boolean
get() = mIndicatorOptions.normalSliderWidth == mIndicatorOptions.checkedSliderWidth
init {
mPaint.isAntiAlias = true
mMeasureResult = MeasureResult()
if (mIndicatorOptions.slideMode == IndicatorSlideMode.SCALE
|| mIndicatorOptions.slideMode == IndicatorSlideMode.COLOR
) {
argbEvaluator = ArgbEvaluator()
}
}
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
): MeasureResult {
maxWidth =
mIndicatorOptions.normalSliderWidth.coerceAtLeast(mIndicatorOptions.checkedSliderWidth)
minWidth =
mIndicatorOptions.normalSliderWidth.coerceAtMost(mIndicatorOptions.checkedSliderWidth)
if (mIndicatorOptions.orientation == IndicatorOrientation.INDICATOR_VERTICAL) {
mMeasureResult.setMeasureResult(measureHeight(), measureWidth())
} else {
mMeasureResult.setMeasureResult(measureWidth(), measureHeight())
}
return mMeasureResult
}
protected open fun measureHeight(): Int {
return mIndicatorOptions.sliderHeight.toInt() + INDICATOR_PADDING
}
private fun measureWidth(): Int {
val pageSize = mIndicatorOptions.pageSize
val indicatorGap = mIndicatorOptions.sliderGap
return ((pageSize - 1) * indicatorGap + maxWidth + (pageSize - 1) * minWidth).toInt() + INDICATOR_PADDING_ADDITION
}
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
}
inner class MeasureResult {
var measureWidth: Int = 0
internal set
var measureHeight: Int = 0
internal set
internal fun setMeasureResult(
measureWidth: Int,
measureHeight: Int
) {
this.measureWidth = measureWidth
this.measureHeight = measureHeight
}
}
}

View File

@@ -0,0 +1,157 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import android.graphics.RectF
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description: Circle Indicator drawer.
</pre> *
*/
class CircleDrawer internal constructor(indicatorOptions: IndicatorOptions) : BaseDrawer(
indicatorOptions
) {
private val rectF = RectF()
override fun measureHeight(): Int {
return maxWidth.toInt() + INDICATOR_PADDING_ADDITION
}
override fun onDraw(canvas: Canvas) {
val pageSize = mIndicatorOptions.pageSize
if (pageSize > 1 || mIndicatorOptions.showIndicatorOneItem && pageSize == 1) {
drawNormal(canvas)
drawSlider(canvas)
}
}
private fun drawNormal(canvas: Canvas) {
val normalIndicatorWidth = mIndicatorOptions.normalSliderWidth
mPaint.color = mIndicatorOptions.normalSliderColor
for (i in 0 until mIndicatorOptions.pageSize) {
val coordinateX = IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, i)
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
drawCircle(canvas, coordinateX, coordinateY, normalIndicatorWidth / 2)
}
}
private fun drawSlider(canvas: Canvas) {
mPaint.color = mIndicatorOptions.checkedSliderColor
when (mIndicatorOptions.slideMode) {
IndicatorSlideMode.NORMAL, IndicatorSlideMode.SMOOTH -> drawCircleSlider(canvas)
IndicatorSlideMode.WORM -> drawWormSlider(canvas)
IndicatorSlideMode.SCALE -> drawScaleSlider(canvas)
IndicatorSlideMode.COLOR -> drawColor(canvas)
}
}
private fun drawColor(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val slideProgress = mIndicatorOptions.slideProgress
val coordinateX = IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
var evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
drawCircle(canvas, coordinateX, coordinateY, mIndicatorOptions.normalSliderWidth / 2)
// 绘制可循环的ViewPager指示器渐变
evaluate = argbEvaluator?.evaluate(
1 - slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
val nextCoordinateX = if (currentPosition == mIndicatorOptions.pageSize - 1) {
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, 0)
} else {
coordinateX + mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
}
drawCircle(canvas, nextCoordinateX, coordinateY, mIndicatorOptions.checkedSliderWidth / 2)
}
private fun drawScaleSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val slideProgress = mIndicatorOptions.slideProgress
val coordinateX = IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
if (slideProgress < 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
val radius =
mIndicatorOptions.checkedSliderWidth / 2 - (mIndicatorOptions.checkedSliderWidth / 2 - mIndicatorOptions.normalSliderWidth / 2) * slideProgress
drawCircle(canvas, coordinateX, coordinateY, radius)
}
if (currentPosition == mIndicatorOptions.pageSize - 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.normalSliderColor, mIndicatorOptions.checkedSliderColor
)
mPaint.color = evaluate as Int
val nextCoordinateX = maxWidth / 2
val nextRadius = minWidth / 2 + (maxWidth / 2 - minWidth / 2) * (slideProgress)
drawCircle(canvas, nextCoordinateX, coordinateY, nextRadius)
} else {
if (slideProgress > 0) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.normalSliderColor, mIndicatorOptions.checkedSliderColor
)
mPaint.color = evaluate as Int
val nextCoordinateX =
coordinateX + mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
val nextRadius =
mIndicatorOptions.normalSliderWidth / 2 + (mIndicatorOptions.checkedSliderWidth / 2 - mIndicatorOptions.normalSliderWidth / 2) * slideProgress
drawCircle(canvas, nextCoordinateX, coordinateY, nextRadius)
}
}
}
private fun drawCircleSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val startCoordinateX =
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val endCoordinateX = IndicatorUtils.getCoordinateX(
mIndicatorOptions, maxWidth, (currentPosition + 1) % mIndicatorOptions.pageSize
)
val coordinateX =
startCoordinateX + (endCoordinateX - startCoordinateX) * mIndicatorOptions.slideProgress
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
val radius = mIndicatorOptions.checkedSliderWidth / 2
drawCircle(canvas, coordinateX, coordinateY, radius)
}
private fun drawWormSlider(canvas: Canvas) {
val sliderHeight = mIndicatorOptions.normalSliderWidth
val slideProgress = mIndicatorOptions.slideProgress
val currentPosition = mIndicatorOptions.currentPosition
val distance = mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
val startCoordinateX =
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val left = startCoordinateX + (distance * (slideProgress - 0.5f) * 2.0f).coerceAtLeast(
0f
) - mIndicatorOptions.normalSliderWidth / 2 + INDICATOR_PADDING
val right = startCoordinateX + (distance * slideProgress * 2f).coerceAtMost(
distance
) + mIndicatorOptions.normalSliderWidth / 2 + INDICATOR_PADDING
rectF.set(left, INDICATOR_PADDING.toFloat(), right, sliderHeight + INDICATOR_PADDING)
canvas.drawRoundRect(rectF, sliderHeight, sliderHeight, mPaint)
}
private fun drawCircle(
canvas: Canvas,
coordinateX: Float,
coordinateY: Float,
radius: Float
) {
canvas.drawCircle(
coordinateX + INDICATOR_PADDING, coordinateY + INDICATOR_PADDING, radius, mPaint
)
}
}

View File

@@ -0,0 +1,20 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description: Dash Indicator Drawer.
</pre> *
*/
class DashDrawer internal constructor(indicatorOptions: IndicatorOptions) : RectDrawer(
indicatorOptions
) {
override fun drawDash(canvas: Canvas) {
canvas.drawRect(mRectF, mPaint)
}
}

View File

@@ -0,0 +1,20 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorStyle
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/24.
* Description: Indicator Drawer Factory.
</pre> *
*/
internal object DrawerFactory {
fun createDrawer(indicatorOptions: IndicatorOptions): IDrawer {
return when (indicatorOptions.indicatorStyle) {
IndicatorStyle.DASH -> DashDrawer(indicatorOptions)
IndicatorStyle.ROUND_RECT -> RoundRectDrawer(indicatorOptions)
else -> CircleDrawer(indicatorOptions)
}
}
}

View File

@@ -0,0 +1,48 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description: Indicator Drawer Proxy.
</pre> *
*/
class DrawerProxy(indicatorOptions: IndicatorOptions) : IDrawer {
private lateinit var mIDrawer: IDrawer
init {
init(indicatorOptions)
}
private fun init(indicatorOptions: IndicatorOptions) {
mIDrawer = DrawerFactory.createDrawer(indicatorOptions)
}
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
}
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
): BaseDrawer.MeasureResult {
return mIDrawer.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas) {
mIDrawer.onDraw(canvas)
}
fun setIndicatorOptions(indicatorOptions: IndicatorOptions) {
init(indicatorOptions)
}
}

View File

@@ -0,0 +1,28 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import com.mogo.och.taxi.passenger.widget.indicator.drawer.BaseDrawer
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description:
</pre> *
*/
interface IDrawer {
fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
)
fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
): BaseDrawer.MeasureResult
fun onDraw(canvas: Canvas)
}

View File

@@ -0,0 +1,219 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.*
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils
/**
* <pre>
* Created by zhpan on 2020/1/17.
* Description:
</pre> *
*/
open class RectDrawer internal constructor(indicatorOptions: IndicatorOptions) : BaseDrawer(
indicatorOptions
) {
internal var mRectF: RectF = RectF()
override fun onDraw(canvas: Canvas) {
val pageSize = mIndicatorOptions.pageSize
if (pageSize > 1 || mIndicatorOptions.showIndicatorOneItem && pageSize == 1) {
if (isWidthEquals && mIndicatorOptions.slideMode != IndicatorSlideMode.NORMAL) {
drawUncheckedSlider(canvas, pageSize)
drawCheckedSlider(canvas)
} else { // 单独处理normalWidth与checkedWidth不一致的情况
if (mIndicatorOptions.slideMode == IndicatorSlideMode.SCALE) {
for (i in 0 until pageSize) {
drawScaleSlider(canvas, i)
}
} else {
drawInequalitySlider(canvas, pageSize)
}
}
}
}
private fun drawScaleSlider(
canvas: Canvas,
i: Int
) {
val checkedColor = mIndicatorOptions.checkedSliderColor
val indicatorGap = mIndicatorOptions.sliderGap
val sliderHeight = mIndicatorOptions.sliderHeight
val currentPosition = mIndicatorOptions.currentPosition
val normalWidth = mIndicatorOptions.normalSliderWidth
val checkedWidth = mIndicatorOptions.checkedSliderWidth
when {
i < currentPosition -> {
mPaint.color = mIndicatorOptions.normalSliderColor
val left: Float = if (currentPosition == mIndicatorOptions.pageSize - 1) {
(i * normalWidth + i * indicatorGap) + (checkedWidth - normalWidth) * mIndicatorOptions.slideProgress
} else {
(i * normalWidth + i * indicatorGap)
}
mRectF.set(left, 0f, left + normalWidth, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
}
i == currentPosition -> {
mPaint.color = checkedColor
val slideProgress = mIndicatorOptions.slideProgress
if (currentPosition == mIndicatorOptions.pageSize - 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
val right =
(mIndicatorOptions.pageSize - 1) * (normalWidth + mIndicatorOptions.sliderGap) + checkedWidth
val left = right - checkedWidth + (checkedWidth - normalWidth) * (slideProgress)
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
} else {
if (slideProgress < 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
val left = i * normalWidth + i * indicatorGap
val right = left + normalWidth + (checkedWidth - normalWidth) * (1 - slideProgress)
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
}
}
if (currentPosition == mIndicatorOptions.pageSize - 1) {
if (slideProgress > 0) {
val evaluate = argbEvaluator?.evaluate(
1 - slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
val left = 0f
val right = left + normalWidth + (checkedWidth - normalWidth) * slideProgress
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
}
} else {
if (slideProgress > 0) {
val evaluate = argbEvaluator?.evaluate(
1 - slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
val right =
i * normalWidth + i * indicatorGap + normalWidth + (indicatorGap + checkedWidth)
val left = right - (normalWidth) - (checkedWidth - normalWidth) * (slideProgress)
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
}
}
}
else -> {
if ((currentPosition + 1 != i || mIndicatorOptions.slideProgress == 0f)) { // 避免多余绘制
mPaint.color = mIndicatorOptions.normalSliderColor
val left = i * minWidth + i * indicatorGap + (checkedWidth - minWidth)
mRectF.set(left, 0f, left + minWidth, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
}
}
}
}
private fun drawUncheckedSlider(
canvas: Canvas,
pageSize: Int
) {
for (i in 0 until pageSize) {
mPaint.color = mIndicatorOptions.normalSliderColor
val left = i * maxWidth + i * +mIndicatorOptions.sliderGap + (maxWidth - minWidth)
mRectF.set(left, 0f, left + minWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight)
}
}
private fun drawInequalitySlider(
canvas: Canvas,
pageSize: Int
) {
var left = 0f
for (i in 0 until pageSize) {
val sliderWidth = (if (i == mIndicatorOptions.currentPosition) maxWidth else minWidth)
mPaint.color =
if (i == mIndicatorOptions.currentPosition) mIndicatorOptions.checkedSliderColor else mIndicatorOptions.normalSliderColor
mRectF.set(left, 0f, left + sliderWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight)
left += sliderWidth + mIndicatorOptions.sliderGap
}
}
private fun drawCheckedSlider(canvas: Canvas) {
mPaint.color = mIndicatorOptions.checkedSliderColor
when (mIndicatorOptions.slideMode) {
IndicatorSlideMode.SMOOTH -> drawSmoothSlider(canvas)
IndicatorSlideMode.WORM -> drawWormSlider(canvas)
IndicatorSlideMode.COLOR -> drawColorSlider(canvas)
}
}
private fun drawColorSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val slideProgress = mIndicatorOptions.slideProgress
val left = currentPosition * minWidth + currentPosition * mIndicatorOptions.sliderGap
if (slideProgress < 0.99) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
mRectF.set(left, 0f, left + minWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight)
}
var nextSliderLeft = left + mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
if (currentPosition == mIndicatorOptions.pageSize - 1) {
nextSliderLeft = 0f
}
val evaluate = argbEvaluator?.evaluate(
1 - slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
mRectF.set(nextSliderLeft, 0f, nextSliderLeft + minWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight)
}
private fun drawWormSlider(canvas: Canvas) {
val sliderHeight = mIndicatorOptions.sliderHeight
val slideProgress = mIndicatorOptions.slideProgress
val currentPosition = mIndicatorOptions.currentPosition
val distance = mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
val startCoordinateX =
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val left = startCoordinateX + (distance * (slideProgress - 0.5f) * 2.0f).coerceAtLeast(
0f
) - mIndicatorOptions.normalSliderWidth / 2
val right = startCoordinateX + (distance * slideProgress * 2f).coerceAtMost(
distance
) + mIndicatorOptions.normalSliderWidth / 2
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
}
private fun drawSmoothSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val indicatorGap = mIndicatorOptions.sliderGap
val sliderHeight = mIndicatorOptions.sliderHeight
val left =
currentPosition * maxWidth + currentPosition * +indicatorGap + (maxWidth + indicatorGap) * mIndicatorOptions.slideProgress
mRectF.set(left, 0f, left + maxWidth, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight)
}
protected open fun drawRoundRect(
canvas: Canvas,
rx: Float,
ry: Float
) {
drawDash(canvas)
}
protected open fun drawDash(canvas: Canvas) {}
}

View File

@@ -0,0 +1,24 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/26.
* Description:
</pre> *
*/
class RoundRectDrawer internal constructor(indicatorOptions: IndicatorOptions) : RectDrawer(
indicatorOptions
) {
override fun drawRoundRect(
canvas: Canvas,
rx: Float,
ry: Float
) {
canvas.drawRoundRect(mRectF, rx, ry, mPaint)
}
}

View File

@@ -0,0 +1,16 @@
package com.mogo.och.taxi.passenger.widget.indicator.enums
import android.widget.LinearLayout
/**
*
* @author zhangpan
* @date 2021/1/21
*/
class IndicatorOrientation {
companion object {
const val INDICATOR_HORIZONTAL = LinearLayout.HORIZONTAL
const val INDICATOR_VERTICAL = LinearLayout.VERTICAL
const val INDICATOR_RTL = 3
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.och.taxi.passenger.widget.indicator.enums
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
interface IndicatorSlideMode {
companion object {
const val NORMAL = 0
const val SMOOTH = 2
const val WORM = 3
const val SCALE = 4
const val COLOR = 5
}
}

View File

@@ -0,0 +1,15 @@
package com.mogo.och.taxi.passenger.widget.indicator.enums
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
interface IndicatorStyle {
companion object {
const val CIRCLE = 0
const val DASH = 1 shl 1
const val ROUND_RECT = 1 shl 2
}
}

View File

@@ -0,0 +1,38 @@
package com.mogo.och.taxi.passenger.widget.indicator.option;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mogo.och.taxi.passenger.R;
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils;
public class AttrsController {
public static void initAttrs(@NonNull Context context, @Nullable AttributeSet attrs,
IndicatorOptions indicatorOptions) {
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView);
int indicatorSlideMode = typedArray.getInt(R.styleable.IndicatorView_vpi_slide_mode, 0);
int indicatorStyle = typedArray.getInt(R.styleable.IndicatorView_vpi_style, 0);
int checkedColor = typedArray.getColor(R.styleable.IndicatorView_vpi_slider_checked_color,
Color.parseColor("#6C6D72"));
int normalColor = typedArray.getColor(R.styleable.IndicatorView_vpi_slider_normal_color,
Color.parseColor("#8C18171C"));
int orientation = typedArray.getInt(R.styleable.IndicatorView_vpi_orientation, 0);
float radius = typedArray.getDimension(R.styleable.IndicatorView_vpi_slider_radius,
IndicatorUtils.dp2px(8));
indicatorOptions.setCheckedColor(checkedColor);
indicatorOptions.setNormalSliderColor(normalColor);
indicatorOptions.setOrientation(orientation);
indicatorOptions.setIndicatorStyle(indicatorStyle);
indicatorOptions.setSlideMode(indicatorSlideMode);
indicatorOptions.setSliderWidth(radius * 2, radius * 2);
typedArray.recycle();
}
}
}

View File

@@ -0,0 +1,108 @@
package com.mogo.och.taxi.passenger.widget.indicator.option
import android.graphics.Color
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorStyle
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils
/**
* <pre>
* Created by zhpan on 2019/11/20.
* Description:Indicator的配置参数
</pre> *
*/
class IndicatorOptions {
@AIndicatorOrientation
var orientation = IndicatorOrientation.INDICATOR_HORIZONTAL
@AIndicatorStyle
var indicatorStyle: Int = 0
/**
* Indicator滑动模式目前仅支持两种
*
* @see IndicatorSlideMode.NORMAL
*
* @see IndicatorSlideMode.SMOOTH
*/
@AIndicatorSlideMode
var slideMode: Int = 0
/**
* 页面size
*/
var pageSize: Int = 0
/**
* 未选中时Indicator颜色
*/
var normalSliderColor: Int = 0
/**
* 选中时Indicator颜色
*/
var checkedSliderColor: Int = 0
/**
* Indicator间距
*/
var sliderGap: Float = 0f
var sliderHeight: Float = 0f
get() = if (field > 0) field else normalSliderWidth / 2
var normalSliderWidth: Float = 0f
var checkedSliderWidth: Float = 0f
/**
* 指示器当前位置
*/
var currentPosition: Int = 0
/**
* 从一个点滑动到另一个点的进度
*/
var slideProgress: Float = 0f
var showIndicatorOneItem: Boolean = false
init {
normalSliderWidth = IndicatorUtils.dp2px(8f)
.toFloat()
checkedSliderWidth = normalSliderWidth
sliderGap = normalSliderWidth
normalSliderColor = Color.parseColor("#8C18171C")
checkedSliderColor = Color.parseColor("#8C6C6D72")
slideMode = IndicatorSlideMode.NORMAL
}
fun setCheckedColor(checkedColor: Int) {
this.checkedSliderColor = checkedColor
}
fun setSliderWidth(
normalIndicatorWidth: Float,
checkedIndicatorWidth: Float
) {
this.normalSliderWidth = normalIndicatorWidth
this.checkedSliderWidth = checkedIndicatorWidth
}
fun setSliderWidth(sliderWidth: Float) {
setSliderWidth(sliderWidth, sliderWidth)
}
fun setSliderColor(
normalColor: Int,
checkedColor: Int
) {
this.normalSliderColor = normalColor
this.checkedSliderColor = checkedColor
}
}

View File

@@ -0,0 +1,32 @@
package com.mogo.och.taxi.passenger.widget.indicator.utils
import android.content.res.Resources
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhangpan on 2020-01-20.
* Description:
</pre> *
*/
object IndicatorUtils {
@JvmStatic
fun dp2px(dpValue: Float): Int {
return (0.5f + dpValue * Resources.getSystem().displayMetrics.density).toInt()
}
fun getCoordinateX(
indicatorOptions: IndicatorOptions,
maxDiameter: Float,
index: Int
): Float {
val normalIndicatorWidth = indicatorOptions.normalSliderWidth
return maxDiameter / 2 + (normalIndicatorWidth + indicatorOptions.sliderGap) * index
}
fun getCoordinateY(maxDiameter: Float): Float {
return maxDiameter / 2
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="#a3000000" android:endColor="#00000000" android:angle="90"/>
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/taxi_order_status_textColor"/>
<size android:width="24px" android:height="24px"/>
</shape>

View File

@@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#8BA3DC"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10,17l5,-5 -5,-5v10z">
</path>
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#314F85"/>
<corners android:radius="@dimen/dp_46"/>
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#3397FC"/>
<corners android:radius="@dimen/dp_46"/>
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="#22e5aa" android:endColor="#038f7e"/>
<corners android:radius="@dimen/dp_46"/>
</shape>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="5px"/>
<solid android:color="#33D8D8D8" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5px"/>
<solid android:color="#66FFFFFF" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="5px"/>
<gradient android:startColor="#2972FF" android:endColor="#27C8FF"/>
</shape>
</clip>
</item>
</layer-list>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv_comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20px"
android:layout_marginBottom="20px"
android:layout_marginStart="10px"
android:layout_marginEnd="10px"
android:background="@drawable/taxi_p_comment_select"
android:button="@null"
android:paddingStart="40px"
android:paddingTop="8px"
android:paddingEnd="40px"
android:paddingBottom="8px"
android:textColor="@color/taxi_order_status_textColor"
android:textSize="40px"
tools:text="非常好" />

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/video_item_player"
android:layout_width="1734px"
android:layout_height="973px" />

View File

@@ -37,22 +37,25 @@
app:shadowColor="#80000000"
app:shadowRadius="60px"
android:visibility="gone"
tools:visibility="visible"
app:shadow_position="outer"
app:xOffset="0px"
app:yOffset="0px">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_comment_contain"
android:layout_width="@dimen/dp_867"
android:layout_height="@dimen/dp_657"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/bg_taxi_p_arrived_info"
app:layout_constraintStart_toStartOf="parent">
<ImageView
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/taxi_p_arrived_end_light"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
android:src="@drawable/taxi_p_arrived_end_light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/taxi_p_arrived_end_light"
@@ -186,6 +189,33 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_comment_list"
app:layout_constraintTop_toBottomOf="@+id/iv_star_hide"
android:layout_marginTop="50px"
android:visibility="invisible"
android:layout_marginStart="@dimen/dp_53"
android:layout_marginEnd="@dimen/dp_53"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_submit"
android:text="提交"
android:gravity="center"
android:visibility="invisible"
android:textColor="@color/taxi_order_status_textColor"
app:layout_constraintTop_toBottomOf="@+id/rv_comment_list"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/taxi_p_comment_submit"
android:textSize="@dimen/sp_46"
android:layout_marginTop="60px"
android:layout_width="400px"
android:layout_marginBottom="80px"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.mogo.och.common.module.wigets.OCHBorderShadowLayout>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cl_contain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cardview_dark_background"
tools:ignore="MissingDefaultResource">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/aciv_title_icon"
android:src="@drawable/taxi_p_mogo_consult_title_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="@dimen/dp_130"
android:layout_marginStart="@dimen/dp_100"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_mogo_consult"
app:layout_constraintTop_toTopOf="@+id/aciv_title_icon"
app:layout_constraintBottom_toBottomOf="@+id/aciv_title_icon"
app:layout_constraintStart_toEndOf="@+id/aciv_title_icon"
android:layout_marginStart="@dimen/dp_13"
android:text="蘑菇咨询"
android:textSize="60px"
android:textColor="@color/taxi_order_status_textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_video_playlist"
android:layout_width="match_parent"
android:layout_height="973px"
android:orientation="horizontal"
android:layout_marginTop="@dimen/dp_130"
android:layout_marginStart="156px"
android:layout_marginEnd="156px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_mogo_consult"/>
<com.mogo.och.taxi.passenger.widget.indicator.IndicatorView
android:id="@+id/indicatorView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rv_video_playlist"
android:layout_marginTop="95px"
app:vpi_orientation="horizontal"
app:vpi_slide_mode="scale"
app:vpi_slider_checked_color="@color/taxi_p_traffic_light_red_color_up"
app:vpi_slider_normal_color="@color/taxi_p_check_keyboard_input_field"
app:vpi_slider_radius="@dimen/dp_20"
app:vpi_style="round_rect" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cl_contain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cardview_dark_background"
tools:ignore="MissingDefaultResource">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/aciv_title_icon"
android:src="@drawable/taxi_p_mogo_movies_title_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="@dimen/dp_130"
android:layout_marginStart="@dimen/dp_100"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_mogo_consult"
app:layout_constraintTop_toTopOf="@+id/aciv_title_icon"
app:layout_constraintBottom_toBottomOf="@+id/aciv_title_icon"
app:layout_constraintStart_toEndOf="@+id/aciv_title_icon"
android:layout_marginStart="@dimen/dp_13"
android:text="影视娱乐"
android:textSize="60px"
android:textColor="@color/taxi_order_status_textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_video_playlist"
android:layout_width="match_parent"
android:layout_height="973px"
android:orientation="horizontal"
android:layout_marginTop="@dimen/dp_130"
android:layout_marginStart="156px"
android:layout_marginEnd="156px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_mogo_consult"/>
<com.mogo.och.taxi.passenger.widget.indicator.IndicatorView
android:id="@+id/indicatorView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rv_video_playlist"
android:layout_marginTop="95px"
app:vpi_orientation="horizontal"
app:vpi_slide_mode="scale"
app:vpi_slider_checked_color="@color/taxi_p_traffic_light_red_color_up"
app:vpi_slider_normal_color="@color/taxi_p_check_keyboard_input_field"
app:vpi_slider_radius="@dimen/dp_20"
app:vpi_style="round_rect" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_video_cover"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/surface_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"/>
<RelativeLayout
android:id="@+id/thumb"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/thumbImage"
android:scaleType="fitXY"
android:background="@drawable/taxi_p_video_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
</RelativeLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/aiv_start_play"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/taxi_p_mogo_video_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_top"
android:layout_width="match_parent"
android:layout_height="158px"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/fullscreen"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="60px"
android:layout_marginEnd="60px"
android:layout_width="66px"
android:layout_height="66px"
android:src="@drawable/taxi_p_change_full" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@+id/fullscreen"
app:layout_constraintBottom_toBottomOf="@+id/fullscreen"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="50px"
android:text="02:23"
android:textColor="@android:color/white"
android:textSize="46px" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!--局部播放器-->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_bottom"
android:layout_width="match_parent"
android:background="@drawable/bg_taxi_p_video_bg"
android:layout_height="158px"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="@+id/start"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/total"
app:layout_constraintBottom_toBottomOf="@+id/total"
android:layout_marginStart="35px"
android:layout_width="@dimen/notice_play_height"
android:layout_height="@dimen/notice_play_height"
android:src="@drawable/notice_video_pause" />
<TextView
android:id="@+id/current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@+id/start"
app:layout_constraintTop_toTopOf="@+id/total"
app:layout_constraintBottom_toBottomOf="@+id/total"
android:layout_marginStart="35px"
android:gravity="bottom"
android:text="02:23"
android:textColor="@android:color/white"
android:textSize="36px" />
<SeekBar
android:id="@+id/progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20px"
android:layout_marginEnd="20px"
app:layout_constraintStart_toEndOf="@+id/current"
app:layout_constraintEnd_toStartOf="@+id/total"
app:layout_constraintTop_toTopOf="@+id/total"
app:layout_constraintBottom_toBottomOf="@+id/total"
android:max="100"
android:maxHeight="10px"
android:minHeight="10px"
android:progressDrawable="@drawable/taxi_video_seekbar_style"
android:thumb="@drawable/bg_taxi_p_video_index" />
<TextView
android:id="@+id/total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/notice_time_bottom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="64px"
android:gravity="bottom"
android:text="08:66"
android:layout_marginEnd="47px"
android:textSize="36px"
android:textColor="@android:color/white"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/taxi_p_left_flow_bg"
android:clickable="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MissingDefaultResource">
<ListView
android:id="@+id/lv_select_item"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="143px"
android:divider="@null"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/v_drag_field"
app:layout_constraintStart_toEndOf="@+id/lv_select_item"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="143px"
android:layout_height="408px">
<androidx.appcompat.widget.AppCompatImageView
android:src="@drawable/ic_baseline_arrow_right_24"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout >

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="IndicatorView">
<attr name="vpi_slider_checked_color" format="color" />
<attr name="vpi_slider_normal_color" format="color" />
<attr name="vpi_slider_radius" format="dimension" />
<attr name="vpi_rtl" format="boolean" />
<attr name="vpi_orientation" format="enum">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
<enum name="rtl" value="3"/>
</attr>
<attr name="vpi_slide_mode" format="enum">
<enum name="normal" value="0" />
<enum name="smooth" value="2" />
<enum name="worm" value="3" />
<enum name="scale" value="4" />
<enum name="color" value="5" />
</attr>
<attr name="vpi_style" format="enum">
<enum name="circle" value="0" />
<enum name="dash" value="2" />
<enum name="round_rect" value="4" />
</attr>
</declare-styleable>
</resources>

View File

@@ -63,14 +63,14 @@ dependencies {
implementation rootProject.ext.dependencies.material
implementation rootProject.ext.dependencies.androidxconstraintlayout
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.androidxrecyclerview
implementation "androidx.recyclerview:recyclerview:1.2.0"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation rootProject.ext.dependencies.gson
implementation project(':libraries:mogo-adas')
implementation project(':libraries:mogo-adas-other')
// implementation 'com.zhidao.support.adas:high:2.7.0.0'
// implementation 'com.zhidao.support.adas:high:2.8.0.0'
// implementation 'com.zhjt.mogo.adas.data:adas-data:2.6.6.0'
compileOnly project(':core:mogo-core-data')
implementation project(':core:mogo-core-utils')

View File

@@ -247,11 +247,11 @@ class MoGoAdasListenerImpl : OnAdasListener {
override fun onPointCloud(header: MessagePad.Header?, pointCloud: MogoPointCloudOuterClass.MogoPointCloud?) {
//点云数据透传
//Logger.d("pointCloud","pointCloud"+pointCloud);
CallerAutopilotPointCloudListenerManager.invokeAutopilotPointCloudDataUpdate(header,pointCloud)
}
override fun onPointCloud(pointCloud: ByteArray?) {
//点云数据透传
CallerAutopilotPointCloudListenerManager.invokeAutopilotPointCloudDataUpdate(pointCloud)
}
//planning障碍物

Some files were not shown because too many files have changed in this diff Show More