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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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?)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使滚动范围在[0,count]内的Helper方法。通常,只有循环布局才需要此方法。
|
||||
*
|
||||
* @param currentScrollPosition 滚动位置范围 个位数 view的index 小数滚动的范围
|
||||
* @param count adapter 中的数量
|
||||
* @return 在[0,总数]范围内滚动位置良好
|
||||
*/
|
||||
private static float makeScrollPositionInRange0ToCount(final float currentScrollPosition, final int count) {
|
||||
float absCurrentScrollPosition = currentScrollPosition;
|
||||
while (0 > absCurrentScrollPosition) {
|
||||
absCurrentScrollPosition += count;
|
||||
}
|
||||
while (Math.round(absCurrentScrollPosition) >= count) {
|
||||
absCurrentScrollPosition -= count;
|
||||
}
|
||||
return absCurrentScrollPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface methods will be called for each visible view item after general LayoutManager layout finishes. <br />
|
||||
* <br />
|
||||
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
|
||||
*/
|
||||
@SuppressWarnings("InterfaceNeverImplemented")
|
||||
public abstract static class PostLayoutListener {
|
||||
|
||||
/**
|
||||
* 子布局完成后调用。通常,您可以在这里进行任何平移和缩放工作。
|
||||
*
|
||||
* @param child view that was layout
|
||||
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
|
||||
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
|
||||
* @param itemPositionInAdapter item position inside adapter for this layout pass
|
||||
*/
|
||||
public ItemTransformation transformChild(
|
||||
@NonNull final View child,
|
||||
final float itemPositionToCenterDiff,
|
||||
final int orientation,
|
||||
final int itemPositionInAdapter
|
||||
) {
|
||||
return transformChild(child, itemPositionToCenterDiff, orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after child layout finished. Generally you can do any translation and scaling work here.
|
||||
*
|
||||
* @param child view that was layout
|
||||
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
|
||||
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
|
||||
*/
|
||||
public ItemTransformation transformChild(
|
||||
@NonNull final View child,
|
||||
final float itemPositionToCenterDiff,
|
||||
final int orientation
|
||||
) {
|
||||
throw new IllegalStateException("at least one transformChild should be implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnCenterItemSelectionListener {
|
||||
|
||||
/**
|
||||
* Listener that will be called on every change of center item.
|
||||
* This listener will be triggered on <b>every</b> layout operation if item was changed.
|
||||
* Do not do any expensive operations in this method since this will effect scroll experience.
|
||||
*
|
||||
* @param adapterPosition current layout center item
|
||||
*/
|
||||
void onCenterItemChanged(final int adapterPosition);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Class for centering items after scroll event.<br />
|
||||
* This class will listen to current scroll state and if item is not centered after scroll it will automatically scroll it to center.
|
||||
*/
|
||||
public class CenterScrollListener extends RecyclerView.OnScrollListener {
|
||||
|
||||
private boolean mAutoSet = true;
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
if (!(layoutManager instanceof CarouselLayoutManager)) {
|
||||
mAutoSet = true;
|
||||
return;
|
||||
}
|
||||
|
||||
final CarouselLayoutManager lm = (CarouselLayoutManager) layoutManager;
|
||||
if (!mAutoSet) {
|
||||
if (RecyclerView.SCROLL_STATE_IDLE == newState) {
|
||||
final int scrollNeeded = lm.getOffsetCenterView();
|
||||
// 滚动到中心位置
|
||||
if (CarouselLayoutManager.HORIZONTAL == lm.getOrientation()) {
|
||||
recyclerView.smoothScrollBy(scrollNeeded, 0);
|
||||
} else {
|
||||
recyclerView.smoothScrollBy(0, scrollNeeded);
|
||||
}
|
||||
pageSelect(recyclerView,newState);
|
||||
mAutoSet = true;
|
||||
}
|
||||
}
|
||||
if (RecyclerView.SCROLL_STATE_DRAGGING == newState || RecyclerView.SCROLL_STATE_SETTLING == newState) {
|
||||
mAutoSet = false;
|
||||
}
|
||||
if(RecyclerView.SCROLL_STATE_DRAGGING == newState){
|
||||
pageStop();
|
||||
}
|
||||
}
|
||||
|
||||
protected void pageStop() {
|
||||
|
||||
}
|
||||
|
||||
protected void pageSelect(RecyclerView recyclerView, final int newState) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
|
||||
|
||||
public class ItemTransformation {
|
||||
|
||||
final float mScaleX;
|
||||
final float mScaleY;
|
||||
final float mTranslationX;
|
||||
final float mTranslationY;
|
||||
|
||||
public ItemTransformation(final float scaleX, final float scaleY, final float translationX, final float translationY) {
|
||||
mScaleX = scaleX;
|
||||
mScaleY = scaleY;
|
||||
mTranslationX = translationX;
|
||||
mTranslationY = translationY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,974 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A cache that uses a bounded amount of space on a filesystem. Each cache
|
||||
* entry has a string key and a fixed number of values. Each key must match
|
||||
* the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
|
||||
* accessible as streams or files. Each value must be between {@code 0} and
|
||||
* {@code Integer.MAX_VALUE} bytes in length.
|
||||
*
|
||||
* <p>The cache stores its data in a directory on the filesystem. This
|
||||
* directory must be exclusive to the cache; the cache may delete or overwrite
|
||||
* files from its directory. It is an error for multiple processes to use the
|
||||
* same cache directory at the same time.
|
||||
*
|
||||
* <p>This cache limits the number of bytes that it will store on the
|
||||
* filesystem. When the number of stored bytes exceeds the limit, the cache will
|
||||
* remove entries in the background until the limit is satisfied. The limit is
|
||||
* not strict: the cache may temporarily exceed it while waiting for files to be
|
||||
* deleted. The limit does not include filesystem overhead or the cache
|
||||
* journal so space-sensitive applications should set a conservative limit.
|
||||
*
|
||||
* <p>Clients call {@link #edit} to create or update the values of an entry. An
|
||||
* entry may have only one editor at one time; if a value is not available to be
|
||||
* edited then {@link #edit} will return null.
|
||||
* <ul>
|
||||
* <li>When an entry is being <strong>created</strong> it is necessary to
|
||||
* supply a full set of values; the empty value should be used as a
|
||||
* placeholder if necessary.
|
||||
* <li>When an entry is being <strong>edited</strong>, it is not necessary
|
||||
* to supply data for every value; values default to their previous
|
||||
* value.
|
||||
* </ul>
|
||||
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
|
||||
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
|
||||
* of values as they were before or after the commit, but never a mix of values.
|
||||
*
|
||||
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
|
||||
* observe the value at the time that {@link #get} was called. Updates and
|
||||
* removals after the call do not impact ongoing reads.
|
||||
*
|
||||
* <p>This class is tolerant of some I/O errors. If files are missing from the
|
||||
* filesystem, the corresponding entries will be dropped from the cache. If
|
||||
* an error occurs while writing a cache value, the edit will fail silently.
|
||||
* Callers should handle other problems by catching {@code IOException} and
|
||||
* responding appropriately.
|
||||
*/
|
||||
final class DiskLruCache implements Closeable {
|
||||
static final String JOURNAL_FILE = "journal";
|
||||
static final String JOURNAL_FILE_TEMP = "journal.tmp";
|
||||
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
|
||||
static final String MAGIC = "libcore.io.DiskLruCache";
|
||||
static final String VERSION_1 = "1";
|
||||
static final long ANY_SEQUENCE_NUMBER = -1;
|
||||
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
|
||||
private static final String CLEAN = "CLEAN";
|
||||
private static final String DIRTY = "DIRTY";
|
||||
private static final String REMOVE = "REMOVE";
|
||||
private static final String READ = "READ";
|
||||
|
||||
/*
|
||||
* This cache uses a journal file named "journal". A typical journal file
|
||||
* looks like this:
|
||||
* libcore.io.DiskLruCache
|
||||
* 1
|
||||
* 100
|
||||
* 2
|
||||
*
|
||||
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
|
||||
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
|
||||
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
|
||||
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
|
||||
* DIRTY 1ab96a171faeeee38496d8b330771a7a
|
||||
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
|
||||
* READ 335c4c6028171cfddfbaae1a9c313c52
|
||||
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
|
||||
*
|
||||
* The first five lines of the journal form its header. They are the
|
||||
* constant string "libcore.io.DiskLruCache", the disk cache's version,
|
||||
* the application's version, the value count, and a blank line.
|
||||
*
|
||||
* Each of the subsequent lines in the file is a record of the state of a
|
||||
* cache entry. Each line contains space-separated values: a state, a key,
|
||||
* and optional state-specific values.
|
||||
* o DIRTY lines track that an entry is actively being created or updated.
|
||||
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
|
||||
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
|
||||
* temporary files may need to be deleted.
|
||||
* o CLEAN lines track a cache entry that has been successfully published
|
||||
* and may be read. A publish line is followed by the lengths of each of
|
||||
* its values.
|
||||
* o READ lines track accesses for LRU.
|
||||
* o REMOVE lines track entries that have been deleted.
|
||||
*
|
||||
* The journal file is appended to as cache operations occur. The journal may
|
||||
* occasionally be compacted by dropping redundant lines. A temporary file named
|
||||
* "journal.tmp" will be used during compaction; that file should be deleted if
|
||||
* it exists when the cache is opened.
|
||||
*/
|
||||
|
||||
private final File directory;
|
||||
private final File journalFile;
|
||||
private final File journalFileTmp;
|
||||
private final File journalFileBackup;
|
||||
private final int appVersion;
|
||||
private long maxSize;
|
||||
private int maxFileCount;
|
||||
private final int valueCount;
|
||||
private long size = 0;
|
||||
private int fileCount = 0;
|
||||
private Writer journalWriter;
|
||||
private final LinkedHashMap<String, Entry> lruEntries =
|
||||
new LinkedHashMap<String, Entry>(0, 0.75f, true);
|
||||
private int redundantOpCount;
|
||||
|
||||
/**
|
||||
* To differentiate between old and current snapshots, each entry is given
|
||||
* a sequence number each time an edit is committed. A snapshot is stale if
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
|
||||
/** This cache uses a single background thread to evict entries. */
|
||||
final ThreadPoolExecutor executorService =
|
||||
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
private final Callable<Void> cleanupCallable = new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
trimToFileCount();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
this.journalFile = new File(directory, JOURNAL_FILE);
|
||||
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
|
||||
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
this.maxFileCount = maxFileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
* there.
|
||||
*
|
||||
* @param directory a writable directory
|
||||
* @param valueCount the number of values per cache entry. Must be positive.
|
||||
* @param maxSize the maximum number of bytes this cache should use to store
|
||||
* @param maxFileCount the maximum file count this cache should store
|
||||
* @throws IOException if reading or writing the cache directory fails
|
||||
*/
|
||||
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount)
|
||||
throws IOException {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
if (maxFileCount <= 0) {
|
||||
throw new IllegalArgumentException("maxFileCount <= 0");
|
||||
}
|
||||
if (valueCount <= 0) {
|
||||
throw new IllegalArgumentException("valueCount <= 0");
|
||||
}
|
||||
|
||||
// If a bkp file exists, use it instead.
|
||||
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
if (backupFile.exists()) {
|
||||
File journalFile = new File(directory, JOURNAL_FILE);
|
||||
// If journal file also exists just delete backup file.
|
||||
if (journalFile.exists()) {
|
||||
backupFile.delete();
|
||||
} else {
|
||||
renameTo(backupFile, journalFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer to pick up where we left off.
|
||||
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
|
||||
if (cache.journalFile.exists()) {
|
||||
try {
|
||||
cache.readJournal();
|
||||
cache.processJournal();
|
||||
cache.journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
|
||||
return cache;
|
||||
} catch (IOException journalIsCorrupt) {
|
||||
System.out
|
||||
.println("DiskLruCache "
|
||||
+ directory
|
||||
+ " is corrupt: "
|
||||
+ journalIsCorrupt.getMessage()
|
||||
+ ", removing");
|
||||
cache.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new empty cache.
|
||||
directory.mkdirs();
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void readJournal() throws IOException {
|
||||
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
|
||||
try {
|
||||
String magic = reader.readLine();
|
||||
String version = reader.readLine();
|
||||
String appVersionString = reader.readLine();
|
||||
String valueCountString = reader.readLine();
|
||||
String blank = reader.readLine();
|
||||
if (!MAGIC.equals(magic)
|
||||
|| !VERSION_1.equals(version)
|
||||
|| !Integer.toString(appVersion).equals(appVersionString)
|
||||
|| !Integer.toString(valueCount).equals(valueCountString)
|
||||
|| !"".equals(blank)) {
|
||||
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
|
||||
+ valueCountString + ", " + blank + "]");
|
||||
}
|
||||
|
||||
int lineCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
readJournalLine(reader.readLine());
|
||||
lineCount++;
|
||||
} catch (EOFException endOfJournal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
redundantOpCount = lineCount - lruEntries.size();
|
||||
} finally {
|
||||
Util.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private void readJournalLine(String line) throws IOException {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
if (firstSpace == -1) {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
|
||||
int keyBegin = firstSpace + 1;
|
||||
int secondSpace = line.indexOf(' ', keyBegin);
|
||||
final String key;
|
||||
if (secondSpace == -1) {
|
||||
key = line.substring(keyBegin);
|
||||
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
|
||||
lruEntries.remove(key);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = line.substring(keyBegin, secondSpace);
|
||||
}
|
||||
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
}
|
||||
|
||||
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
|
||||
String[] parts = line.substring(secondSpace + 1).split(" ");
|
||||
entry.readable = true;
|
||||
entry.currentEditor = null;
|
||||
entry.setLengths(parts);
|
||||
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
|
||||
entry.currentEditor = new Editor(entry);
|
||||
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
|
||||
// This work was already done by calling lruEntries.get().
|
||||
} else {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the initial size and collects garbage as a part of opening the
|
||||
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
|
||||
*/
|
||||
private void processJournal() throws IOException {
|
||||
deleteIfExists(journalFileTmp);
|
||||
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
|
||||
Entry entry = i.next();
|
||||
if (entry.currentEditor == null) {
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
size += entry.lengths[t];
|
||||
fileCount++;
|
||||
}
|
||||
} else {
|
||||
entry.currentEditor = null;
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
deleteIfExists(entry.getCleanFile(t));
|
||||
deleteIfExists(entry.getDirtyFile(t));
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new journal that omits redundant information. This replaces the
|
||||
* current journal if it exists.
|
||||
*/
|
||||
private synchronized void rebuildJournal() throws IOException {
|
||||
if (journalWriter != null) {
|
||||
journalWriter.close();
|
||||
}
|
||||
|
||||
Writer writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
|
||||
try {
|
||||
writer.write(MAGIC);
|
||||
writer.write("\n");
|
||||
writer.write(VERSION_1);
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(appVersion));
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(valueCount));
|
||||
writer.write("\n");
|
||||
writer.write("\n");
|
||||
|
||||
for (Entry entry : lruEntries.values()) {
|
||||
if (entry.currentEditor != null) {
|
||||
writer.write(DIRTY + ' ' + entry.key + '\n');
|
||||
} else {
|
||||
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
if (journalFile.exists()) {
|
||||
renameTo(journalFile, journalFileBackup, true);
|
||||
}
|
||||
renameTo(journalFileTmp, journalFile, false);
|
||||
journalFileBackup.delete();
|
||||
|
||||
journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
}
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
|
||||
* exist is not currently readable. If a value is returned, it is moved to
|
||||
* the head of the LRU queue.
|
||||
*/
|
||||
public synchronized Snapshot get(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Open all streams eagerly to guarantee that we see a single published
|
||||
// snapshot. If we opened streams lazily then the streams could come
|
||||
// from different edits.
|
||||
File[] files = new File[valueCount];
|
||||
InputStream[] ins = new InputStream[valueCount];
|
||||
try {
|
||||
File file;
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
file = entry.getCleanFile(i);
|
||||
files[i] = file;
|
||||
ins[i] = new FileInputStream(file);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// A file must have been deleted manually!
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (ins[i] != null) {
|
||||
Util.closeQuietly(ins[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(READ + ' ' + key + '\n');
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for the entry named {@code key}, or null if another
|
||||
* edit is in progress.
|
||||
*/
|
||||
public Editor edit(String key) throws IOException {
|
||||
return edit(key, ANY_SEQUENCE_NUMBER);
|
||||
}
|
||||
|
||||
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|
||||
|| entry.sequenceNumber != expectedSequenceNumber)) {
|
||||
return null; // Snapshot is stale.
|
||||
}
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
} else if (entry.currentEditor != null) {
|
||||
return null; // Another edit is in progress.
|
||||
}
|
||||
|
||||
Editor editor = new Editor(entry);
|
||||
entry.currentEditor = editor;
|
||||
|
||||
// Flush the journal before creating files to prevent file leaks.
|
||||
journalWriter.write(DIRTY + ' ' + key + '\n');
|
||||
journalWriter.flush();
|
||||
return editor;
|
||||
}
|
||||
|
||||
/** Returns the directory where this cache stores its data. */
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes that this cache should use to store
|
||||
* its data.
|
||||
*/
|
||||
public synchronized long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/** Returns the maximum number of files that this cache should store */
|
||||
public synchronized int getMaxFileCount() {
|
||||
return maxFileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum number of bytes the cache can store and queues a job
|
||||
* to trim the existing store, if necessary.
|
||||
*/
|
||||
public synchronized void setMaxSize(long maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes currently being used to store the values in
|
||||
* this cache. This may be greater than the max size if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of files currently being used to store the values in
|
||||
* this cache. This may be greater than the max file count if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long fileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
|
||||
Entry entry = editor.entry;
|
||||
if (entry.currentEditor != editor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// If this edit is creating the entry for the first time, every index must have a value.
|
||||
if (success && !entry.readable) {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (!editor.written[i]) {
|
||||
editor.abort();
|
||||
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
|
||||
}
|
||||
if (!entry.getDirtyFile(i).exists()) {
|
||||
editor.abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File dirty = entry.getDirtyFile(i);
|
||||
if (success) {
|
||||
if (dirty.exists()) {
|
||||
File clean = entry.getCleanFile(i);
|
||||
dirty.renameTo(clean);
|
||||
long oldLength = entry.lengths[i];
|
||||
long newLength = clean.length();
|
||||
entry.lengths[i] = newLength;
|
||||
size = size - oldLength + newLength;
|
||||
fileCount++;
|
||||
}
|
||||
} else {
|
||||
deleteIfExists(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
entry.currentEditor = null;
|
||||
if (entry.readable | success) {
|
||||
entry.readable = true;
|
||||
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
if (success) {
|
||||
entry.sequenceNumber = nextSequenceNumber++;
|
||||
}
|
||||
} else {
|
||||
lruEntries.remove(entry.key);
|
||||
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
|
||||
}
|
||||
journalWriter.flush();
|
||||
|
||||
if (size > maxSize || fileCount > maxFileCount || journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only rebuild the journal when it will halve the size of the journal
|
||||
* and eliminate at least 2000 ops.
|
||||
*/
|
||||
private boolean journalRebuildRequired() {
|
||||
final int redundantOpCompactThreshold = 2000;
|
||||
return redundantOpCount >= redundantOpCompactThreshold //
|
||||
&& redundantOpCount >= lruEntries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the entry for {@code key} if it exists and can be removed. Entries
|
||||
* actively being edited cannot be removed.
|
||||
*
|
||||
* @return true if an entry was removed.
|
||||
*/
|
||||
public synchronized boolean remove(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null || entry.currentEditor != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File file = entry.getCleanFile(i);
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException("failed to delete " + file);
|
||||
}
|
||||
size -= entry.lengths[i];
|
||||
fileCount--;
|
||||
entry.lengths[i] = 0;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(REMOVE + ' ' + key + '\n');
|
||||
lruEntries.remove(key);
|
||||
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns true if this cache has been closed. */
|
||||
public synchronized boolean isClosed() {
|
||||
return journalWriter == null;
|
||||
}
|
||||
|
||||
private void checkNotClosed() {
|
||||
if (journalWriter == null) {
|
||||
throw new IllegalStateException("cache is closed");
|
||||
}
|
||||
}
|
||||
|
||||
/** Force buffered operations to the filesystem. */
|
||||
public synchronized void flush() throws IOException {
|
||||
checkNotClosed();
|
||||
trimToSize();
|
||||
trimToFileCount();
|
||||
journalWriter.flush();
|
||||
}
|
||||
|
||||
/** Closes this cache. Stored values will remain on the filesystem. */
|
||||
public synchronized void close() throws IOException {
|
||||
if (journalWriter == null) {
|
||||
return; // Already closed.
|
||||
}
|
||||
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
|
||||
if (entry.currentEditor != null) {
|
||||
entry.currentEditor.abort();
|
||||
}
|
||||
}
|
||||
trimToSize();
|
||||
trimToFileCount();
|
||||
journalWriter.close();
|
||||
journalWriter = null;
|
||||
}
|
||||
|
||||
private void trimToSize() throws IOException {
|
||||
while (size > maxSize) {
|
||||
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private void trimToFileCount() throws IOException {
|
||||
while (fileCount > maxFileCount) {
|
||||
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete
|
||||
* all files in the cache directory including files that weren't created by
|
||||
* the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
close();
|
||||
Util.deleteContents(directory);
|
||||
}
|
||||
|
||||
private void validateKey(String key) {
|
||||
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
}
|
||||
|
||||
/** A snapshot of the values for an entry. */
|
||||
public final class Snapshot implements Closeable {
|
||||
private final String key;
|
||||
private final long sequenceNumber;
|
||||
private File[] files;
|
||||
private final InputStream[] ins;
|
||||
private final long[] lengths;
|
||||
|
||||
private Snapshot(String key, long sequenceNumber, File[] files, InputStream[] ins, long[] lengths) {
|
||||
this.key = key;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.files = files;
|
||||
this.ins = ins;
|
||||
this.lengths = lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for this snapshot's entry, or null if either the
|
||||
* entry has changed since this snapshot was created or if another edit
|
||||
* is in progress.
|
||||
*/
|
||||
public Editor edit() throws IOException {
|
||||
return DiskLruCache.this.edit(key, sequenceNumber);
|
||||
}
|
||||
|
||||
/** Returns file with the value for {@code index}. */
|
||||
public File getFile(int index) {
|
||||
return files[index];
|
||||
}
|
||||
|
||||
/** Returns the unbuffered stream with the value for {@code index}. */
|
||||
public InputStream getInputStream(int index) {
|
||||
return ins[index];
|
||||
}
|
||||
|
||||
/** Returns the string value for {@code index}. */
|
||||
public String getString(int index) throws IOException {
|
||||
return inputStreamToString(getInputStream(index));
|
||||
}
|
||||
|
||||
/** Returns the byte length of the value for {@code index}. */
|
||||
public long getLength(int index) {
|
||||
return lengths[index];
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (InputStream in : ins) {
|
||||
Util.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
// Eat all writes silently. Nom nom.
|
||||
}
|
||||
};
|
||||
|
||||
/** Edits the values for an entry. */
|
||||
public final class Editor {
|
||||
private final Entry entry;
|
||||
private final boolean[] written;
|
||||
private boolean hasErrors;
|
||||
private boolean committed;
|
||||
|
||||
private Editor(Entry entry) {
|
||||
this.entry = entry;
|
||||
this.written = (entry.readable) ? null : new boolean[valueCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unbuffered input stream to read the last committed value,
|
||||
* or null if no value has been committed.
|
||||
*/
|
||||
public InputStream newInputStream(int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new FileInputStream(entry.getCleanFile(index));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last committed value as a string, or null if no value
|
||||
* has been committed.
|
||||
*/
|
||||
public String getString(int index) throws IOException {
|
||||
InputStream in = newInputStream(index);
|
||||
return in != null ? inputStreamToString(in) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new unbuffered output stream to write the value at
|
||||
* {@code index}. If the underlying output stream encounters errors
|
||||
* when writing to the filesystem, this edit will be aborted when
|
||||
* {@link #commit} is called. The returned output stream does not throw
|
||||
* IOExceptions.
|
||||
*/
|
||||
public OutputStream newOutputStream(int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
written[index] = true;
|
||||
}
|
||||
File dirtyFile = entry.getDirtyFile(index);
|
||||
FileOutputStream outputStream;
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Attempt to recreate the cache directory.
|
||||
directory.mkdirs();
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e2) {
|
||||
// We are unable to recover. Silently eat the writes.
|
||||
return NULL_OUTPUT_STREAM;
|
||||
}
|
||||
}
|
||||
return new FaultHidingOutputStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the value at {@code index} to {@code value}. */
|
||||
public void set(int index, String value) throws IOException {
|
||||
Writer writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
|
||||
writer.write(value);
|
||||
} finally {
|
||||
Util.closeQuietly(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits this edit so it is visible to readers. This releases the
|
||||
* edit lock so another edit may be started on the same key.
|
||||
*/
|
||||
public void commit() throws IOException {
|
||||
if (hasErrors) {
|
||||
completeEdit(this, false);
|
||||
remove(entry.key); // The previous entry is stale.
|
||||
} else {
|
||||
completeEdit(this, true);
|
||||
}
|
||||
committed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts this edit. This releases the edit lock so another edit may be
|
||||
* started on the same key.
|
||||
*/
|
||||
public void abort() throws IOException {
|
||||
completeEdit(this, false);
|
||||
}
|
||||
|
||||
public void abortUnlessCommitted() {
|
||||
if (!committed) {
|
||||
try {
|
||||
abort();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FaultHidingOutputStream extends FilterOutputStream {
|
||||
private FaultHidingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override public void write(int oneByte) {
|
||||
try {
|
||||
out.write(oneByte);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void write(byte[] buffer, int offset, int length) {
|
||||
try {
|
||||
out.write(buffer, offset, length);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void flush() {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
private final String key;
|
||||
|
||||
/** Lengths of this entry's files. */
|
||||
private final long[] lengths;
|
||||
|
||||
/** True if this entry has ever been published. */
|
||||
private boolean readable;
|
||||
|
||||
/** The ongoing edit or null if this entry is not being edited. */
|
||||
private Editor currentEditor;
|
||||
|
||||
/** The sequence number of the most recently committed edit to this entry. */
|
||||
private long sequenceNumber;
|
||||
|
||||
private Entry(String key) {
|
||||
this.key = key;
|
||||
this.lengths = new long[valueCount];
|
||||
}
|
||||
|
||||
public String getLengths() throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (long size : lengths) {
|
||||
result.append(' ').append(size);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Set lengths using decimal numbers like "10123". */
|
||||
private void setLengths(String[] strings) throws IOException {
|
||||
if (strings.length != valueCount) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
lengths[i] = Long.parseLong(strings[i]);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
}
|
||||
|
||||
private IOException invalidLengths(String[] strings) throws IOException {
|
||||
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
|
||||
}
|
||||
|
||||
public File getCleanFile(int i) {
|
||||
return new File(directory, key + "" + i);
|
||||
}
|
||||
|
||||
public File getDirtyFile(int i) {
|
||||
return new File(directory, key + "" + i + ".tmp");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/*******************************************************************************
|
||||
* Description: 用于缓存经过高斯模糊的图片
|
||||
*
|
||||
* Author: Freeman
|
||||
*
|
||||
* Date: 2018/9/4
|
||||
*
|
||||
* Copyright: all rights reserved by Freeman.
|
||||
*******************************************************************************/
|
||||
public class DiskLruCacheManager {
|
||||
|
||||
private DiskLruCache diskLruCache;
|
||||
private static DiskLruCacheManager instance;
|
||||
|
||||
private final int MAX_CACHE_SIZE = 64 * 1024 * 1024;
|
||||
|
||||
private DiskLruCacheManager(Context context) {
|
||||
try {
|
||||
diskLruCache = DiskLruCache.open(context.getCacheDir(), 1, 1,
|
||||
MAX_CACHE_SIZE, Integer.MAX_VALUE);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
public static DiskLruCacheManager getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
synchronized (DiskLruCacheManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new DiskLruCacheManager(context.getApplicationContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void put(String url, Bitmap bitmap) {
|
||||
if (TextUtils.isEmpty(url) || bitmap == null || bitmap.isRecycled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DiskLruCache.Editor editor = diskLruCache.edit(getKey(url));
|
||||
OutputStream outputStream = editor.newOutputStream(0);
|
||||
if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) {
|
||||
editor.commit();
|
||||
}
|
||||
diskLruCache.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap get(String url) {
|
||||
try {
|
||||
DiskLruCache.Snapshot snapshot = diskLruCache.get(getKey(url));
|
||||
if (snapshot != null) {
|
||||
InputStream inputStream = snapshot.getInputStream(0);
|
||||
return BitmapFactory.decodeStream(inputStream);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getKey(String url) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
byte[] md5 = digest.digest(url.getBytes());
|
||||
BigInteger bigInteger = new BigInteger(1, md5);
|
||||
return bigInteger.toString(16);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
diskLruCache.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/**
|
||||
* Created by jay on 11/7/15.
|
||||
*/
|
||||
public class FastBlurUtil {
|
||||
|
||||
public static Bitmap doBlur(Bitmap sentBitmap, int scaleRadius, int radius) {
|
||||
|
||||
// Stack Blur v1.0 from
|
||||
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
|
||||
//
|
||||
// Java Author: Mario Klingemann <mario at quasimondo.com>
|
||||
// http://incubator.quasimondo.com
|
||||
// created Feburary 29, 2004
|
||||
// Android port : Yahel Bouaziz <yahel at kayenko.com>
|
||||
// http://www.kayenko.com
|
||||
// ported april 5th, 2012
|
||||
|
||||
// This is a compromise between Gaussian Blur and Box blur
|
||||
// It creates much better looking blurs than Box Blur, but is
|
||||
// 7x faster than my Gaussian Blur implementation.
|
||||
//
|
||||
// I called it Stack Blur because this describes best how this
|
||||
// filter works internally: it creates a kind of moving stack
|
||||
// of colors whilst scanning through the image. Thereby it
|
||||
// just has to add one new block of color to the right side
|
||||
// of the stack and remove the leftmost color. The remaining
|
||||
// colors on the topmost layer of the stack are either added on
|
||||
// or reduced by one, depending on if they are on the right or
|
||||
// on the left side of the stack.
|
||||
//
|
||||
// If you are using this algorithm in your code please add
|
||||
// the following line:
|
||||
//
|
||||
// Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
|
||||
if (scaleRadius > 0) {
|
||||
sentBitmap = Bitmap.createScaledBitmap(sentBitmap, sentBitmap.getWidth() / scaleRadius,
|
||||
sentBitmap.getHeight() / scaleRadius, false);
|
||||
}
|
||||
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
|
||||
|
||||
if (radius < 1) {
|
||||
return (null);
|
||||
}
|
||||
int w = bitmap.getWidth();
|
||||
int h = bitmap.getHeight();
|
||||
|
||||
int[] pix = new int[w * h];
|
||||
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
|
||||
|
||||
int wm = w - 1;
|
||||
int hm = h - 1;
|
||||
int wh = w * h;
|
||||
int div = radius + radius + 1;
|
||||
|
||||
int r[] = new int[wh];
|
||||
int g[] = new int[wh];
|
||||
int b[] = new int[wh];
|
||||
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
|
||||
int vmin[] = new int[Math.max(w, h)];
|
||||
|
||||
int divsum = (div + 1) >> 1;
|
||||
divsum *= divsum;
|
||||
int dv[] = new int[256 * divsum];
|
||||
for (i = 0; i < 256 * divsum; i++) {
|
||||
dv[i] = (i / divsum);
|
||||
}
|
||||
|
||||
yw = yi = 0;
|
||||
|
||||
int[][] stack = new int[div][3];
|
||||
int stackpointer;
|
||||
int stackstart;
|
||||
int[] sir;
|
||||
int rbs;
|
||||
int r1 = radius + 1;
|
||||
int routsum, goutsum, boutsum;
|
||||
int rinsum, ginsum, binsum;
|
||||
|
||||
for (y = 0; y < h; y++) {
|
||||
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
|
||||
for (i = -radius; i <= radius; i++) {
|
||||
p = pix[yi + Math.min(wm, Math.max(i, 0))];
|
||||
sir = stack[i + radius];
|
||||
sir[0] = (p & 0xff0000) >> 16;
|
||||
sir[1] = (p & 0x00ff00) >> 8;
|
||||
sir[2] = (p & 0x0000ff);
|
||||
rbs = r1 - Math.abs(i);
|
||||
rsum += sir[0] * rbs;
|
||||
gsum += sir[1] * rbs;
|
||||
bsum += sir[2] * rbs;
|
||||
if (i > 0) {
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
} else {
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
}
|
||||
}
|
||||
stackpointer = radius;
|
||||
|
||||
for (x = 0; x < w; x++) {
|
||||
|
||||
r[yi] = dv[rsum];
|
||||
g[yi] = dv[gsum];
|
||||
b[yi] = dv[bsum];
|
||||
|
||||
rsum -= routsum;
|
||||
gsum -= goutsum;
|
||||
bsum -= boutsum;
|
||||
|
||||
stackstart = stackpointer - radius + div;
|
||||
sir = stack[stackstart % div];
|
||||
|
||||
routsum -= sir[0];
|
||||
goutsum -= sir[1];
|
||||
boutsum -= sir[2];
|
||||
|
||||
if (y == 0) {
|
||||
vmin[x] = Math.min(x + radius + 1, wm);
|
||||
}
|
||||
p = pix[yw + vmin[x]];
|
||||
|
||||
sir[0] = (p & 0xff0000) >> 16;
|
||||
sir[1] = (p & 0x00ff00) >> 8;
|
||||
sir[2] = (p & 0x0000ff);
|
||||
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
|
||||
rsum += rinsum;
|
||||
gsum += ginsum;
|
||||
bsum += binsum;
|
||||
|
||||
stackpointer = (stackpointer + 1) % div;
|
||||
sir = stack[(stackpointer) % div];
|
||||
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
|
||||
rinsum -= sir[0];
|
||||
ginsum -= sir[1];
|
||||
binsum -= sir[2];
|
||||
|
||||
yi++;
|
||||
}
|
||||
yw += w;
|
||||
}
|
||||
for (x = 0; x < w; x++) {
|
||||
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
|
||||
yp = -radius * w;
|
||||
for (i = -radius; i <= radius; i++) {
|
||||
yi = Math.max(0, yp) + x;
|
||||
|
||||
sir = stack[i + radius];
|
||||
|
||||
sir[0] = r[yi];
|
||||
sir[1] = g[yi];
|
||||
sir[2] = b[yi];
|
||||
|
||||
rbs = r1 - Math.abs(i);
|
||||
|
||||
rsum += r[yi] * rbs;
|
||||
gsum += g[yi] * rbs;
|
||||
bsum += b[yi] * rbs;
|
||||
|
||||
if (i > 0) {
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
} else {
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
}
|
||||
|
||||
if (i < hm) {
|
||||
yp += w;
|
||||
}
|
||||
}
|
||||
yi = x;
|
||||
stackpointer = radius;
|
||||
for (y = 0; y < h; y++) {
|
||||
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
|
||||
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
|
||||
|
||||
rsum -= routsum;
|
||||
gsum -= goutsum;
|
||||
bsum -= boutsum;
|
||||
|
||||
stackstart = stackpointer - radius + div;
|
||||
sir = stack[stackstart % div];
|
||||
|
||||
routsum -= sir[0];
|
||||
goutsum -= sir[1];
|
||||
boutsum -= sir[2];
|
||||
|
||||
if (x == 0) {
|
||||
vmin[y] = Math.min(y + r1, hm) * w;
|
||||
}
|
||||
p = x + vmin[y];
|
||||
|
||||
sir[0] = r[p];
|
||||
sir[1] = g[p];
|
||||
sir[2] = b[p];
|
||||
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
|
||||
rsum += rinsum;
|
||||
gsum += ginsum;
|
||||
bsum += binsum;
|
||||
|
||||
stackpointer = (stackpointer + 1) % div;
|
||||
sir = stack[stackpointer];
|
||||
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
|
||||
rinsum -= sir[0];
|
||||
ginsum -= sir[1];
|
||||
binsum -= sir[2];
|
||||
|
||||
yi += w;
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
|
||||
|
||||
return (bitmap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class GlideBlurTransform extends BitmapTransformation {
|
||||
|
||||
private String key;
|
||||
private Context context;
|
||||
private int blurRadius;
|
||||
|
||||
public GlideBlurTransform(Context context, String key, int blurRadius ) {
|
||||
this.context = context;
|
||||
this.key = key;
|
||||
this.blurRadius = blurRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap transform( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight ) {
|
||||
Bitmap bitmap = FastBlurUtil.doBlur( toTransform, 1, blurRadius );
|
||||
// 缓存高斯模糊图片
|
||||
DiskLruCacheManager.getInstance( context ).put( key, bitmap );
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey( MessageDigest messageDigest ) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Buffers input from an {@link InputStream} for reading lines.
|
||||
*
|
||||
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
|
||||
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
|
||||
* line at end of input is invalid and will be ignored, the caller may use {@code
|
||||
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
|
||||
*
|
||||
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
|
||||
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
|
||||
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
|
||||
* end-of-input reporting and a more restrictive definition of a line.
|
||||
*
|
||||
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
|
||||
* and 10, respectively, and the representation of no other character contains these values.
|
||||
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
|
||||
* The default charset is US_ASCII.
|
||||
*/
|
||||
class StrictLineReader implements Closeable {
|
||||
private static final byte CR = (byte) '\r';
|
||||
private static final byte LF = (byte) '\n';
|
||||
|
||||
private final InputStream in;
|
||||
private final Charset charset;
|
||||
|
||||
/*
|
||||
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
|
||||
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
|
||||
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
|
||||
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
|
||||
*/
|
||||
private byte[] buf;
|
||||
private int pos;
|
||||
private int end;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
|
||||
*
|
||||
* @param in the {@code InputStream} to read data from.
|
||||
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
|
||||
* supported.
|
||||
* @throws NullPointerException if {@code in} or {@code charset} is null.
|
||||
* @throws IllegalArgumentException if the specified charset is not supported.
|
||||
*/
|
||||
public StrictLineReader(InputStream in, Charset charset) {
|
||||
this(in, 8192, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LineReader} with the specified capacity and charset.
|
||||
*
|
||||
* @param in the {@code InputStream} to read data from.
|
||||
* @param capacity the capacity of the buffer.
|
||||
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
|
||||
* supported.
|
||||
* @throws NullPointerException if {@code in} or {@code charset} is null.
|
||||
* @throws IllegalArgumentException if {@code capacity} is negative or zero
|
||||
* or the specified charset is not supported.
|
||||
*/
|
||||
public StrictLineReader(InputStream in, int capacity, Charset charset) {
|
||||
if (in == null || charset == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (capacity < 0) {
|
||||
throw new IllegalArgumentException("capacity <= 0");
|
||||
}
|
||||
if (!(charset.equals(Util.US_ASCII))) {
|
||||
throw new IllegalArgumentException("Unsupported encoding");
|
||||
}
|
||||
|
||||
this.in = in;
|
||||
this.charset = charset;
|
||||
buf = new byte[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the reader by closing the underlying {@code InputStream} and
|
||||
* marking this reader as closed.
|
||||
*
|
||||
* @throws IOException for errors when closing the underlying {@code InputStream}.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
synchronized (in) {
|
||||
if (buf != null) {
|
||||
buf = null;
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
|
||||
* this end of line marker is not included in the result.
|
||||
*
|
||||
* @return the next line from the input.
|
||||
* @throws IOException for underlying {@code InputStream} errors.
|
||||
* @throws EOFException for the end of source stream.
|
||||
*/
|
||||
public String readLine() throws IOException {
|
||||
synchronized (in) {
|
||||
if (buf == null) {
|
||||
throw new IOException("LineReader is closed");
|
||||
}
|
||||
|
||||
// Read more data if we are at the end of the buffered data.
|
||||
// Though it's an error to read after an exception, we will let {@code fillBuf()}
|
||||
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
|
||||
if (pos >= end) {
|
||||
fillBuf();
|
||||
}
|
||||
// Try to find LF in the buffered data and return the line if successful.
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
|
||||
String res = new String(buf, pos, lineEnd - pos, charset.name());
|
||||
pos = i + 1;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's anticipate up to 80 characters on top of those already read.
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
|
||||
@Override
|
||||
public String toString() {
|
||||
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
||||
try {
|
||||
return new String(buf, 0, length, charset.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // Since we control the charset this will never happen.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
out.write(buf, pos, end - pos);
|
||||
// Mark unterminated line in case fillBuf throws EOFException or IOException.
|
||||
end = -1;
|
||||
fillBuf();
|
||||
// Try to find LF in the buffered data and return the line if successful.
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
if (i != pos) {
|
||||
out.write(buf, pos, i - pos);
|
||||
}
|
||||
pos = i + 1;
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads new input data into the buffer. Call only with pos == end or end == -1,
|
||||
* depending on the desired outcome if the function throws.
|
||||
*/
|
||||
private void fillBuf() throws IOException {
|
||||
int result = in.read(buf, 0, buf.length);
|
||||
if (result == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
pos = 0;
|
||||
end = result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mogo.och.taxi.passenger.utils.blur;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/** Junk drawer of utility methods. */
|
||||
final class Util {
|
||||
static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||
static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private Util() {
|
||||
}
|
||||
|
||||
static String readFully(Reader reader) throws IOException {
|
||||
try {
|
||||
StringWriter writer = new StringWriter();
|
||||
char[] buffer = new char[1024];
|
||||
int count;
|
||||
while ((count = reader.read(buffer)) != -1) {
|
||||
writer.write(buffer, 0, count);
|
||||
}
|
||||
return writer.toString();
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the contents of {@code dir}. Throws an IOException if any file
|
||||
* could not be deleted, or if {@code dir} is not a readable directory.
|
||||
*/
|
||||
static void deleteContents(File dir) throws IOException {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
throw new IOException("not a readable directory: " + dir);
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("failed to delete file: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void closeQuietly(/*Auto*/Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 901 B |
|
After Width: | Height: | Size: 888 B |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 901 B |
|
After Width: | Height: | Size: 888 B |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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="非常好" />
|
||||
@@ -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" />
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 >
|
||||
31
OCH/mogo-och-taxi-passenger/src/main/res/values/attrs.xml
Normal 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>
|
||||
@@ -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')
|
||||
|
||||
@@ -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障碍物
|
||||
|
||||