diff --git a/OCH/taxi/unmanned-driver/src/main/AndroidManifest.xml b/OCH/taxi/unmanned-driver/src/main/AndroidManifest.xml
index fb42392054..3ab0402e38 100644
--- a/OCH/taxi/unmanned-driver/src/main/AndroidManifest.xml
+++ b/OCH/taxi/unmanned-driver/src/main/AndroidManifest.xml
@@ -1,5 +1,9 @@
-
- /
+
+
+
\ No newline at end of file
diff --git a/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/bean/TaxiRoutingQueryLineResponse.java b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/bean/TaxiRoutingQueryLineResponse.java
new file mode 100644
index 0000000000..7c3efdda76
--- /dev/null
+++ b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/bean/TaxiRoutingQueryLineResponse.java
@@ -0,0 +1,22 @@
+package com.mogo.och.taxi.bean;
+
+import com.mogo.eagle.core.data.BaseData;
+
+import java.util.List;
+
+/**
+ * @author: wangmingjun
+ * @date: 2022/2/9
+ */
+public class TaxiRoutingQueryLineResponse extends BaseData {
+ public List data;
+
+ public static class Result {
+ public int lineId;//线路id
+ public String name;//线路名字
+ public int todayVerifyNum;//本车今天验证次数
+ public int historyVerifyTotalUsableNum;//历史累计验证可用次数
+ public int historyVerifyTotalNotUsableNum;//历史累计验证不可用次数
+ public boolean isChoosed;//当前是否选中
+ }
+}
diff --git a/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/base/TaxiFragment.kt b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/base/TaxiFragment.kt
index 7b83ee52a7..18ecb7806e 100644
--- a/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/base/TaxiFragment.kt
+++ b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/base/TaxiFragment.kt
@@ -8,11 +8,13 @@ import android.view.ViewGroup
import androidx.fragment.app.FragmentTransaction
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.commons.module.status.MogoStatusManager
+import com.mogo.commons.module.status.StatusDescriptor
import com.mogo.eagle.core.data.temp.EventLogout
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.e
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.ToastUtils
+import com.mogo.eagle.core.utilcode.util.UiThreadHandler
import com.mogo.och.common.module.biz.constant.OchCommonConst
import com.mogo.och.common.module.biz.provider.LoginService
import com.mogo.och.common.module.utils.FlowBus
@@ -21,6 +23,7 @@ import com.mogo.och.taxi.constant.TaxiDriverEventConst
import com.mogo.och.taxi.constant.TaxiOrderStatusEnum
import com.mogo.och.taxi.network.TaxiDriverLoginImpl
import com.mogo.och.taxi.ui.operational.TaxiOperationalDialogFragment
+import com.mogo.och.taxi.ui.routing.TaxiRoutingFragment
import com.mogo.och.taxi.ui.task.TaxiTaskModel
import com.mogo.och.taxi.ui.task.TaxiTaskTabFragment
import com.mogo.och.taxi.utils.TPRouteDataTestUtils
@@ -45,6 +48,7 @@ class TaxiFragment : BaseTaxiTabFragment(),
private var taskTabFragment: WeakReference? = null
private var personalDialogFragment: WeakReference? = null
+ private var routingVerifyFragment: WeakReference? = null
private var loginService: LoginService? = null
@Subscribe(threadMode = ThreadMode.MAIN)
@@ -151,15 +155,53 @@ class TaxiFragment : BaseTaxiTabFragment(),
}
private fun initFragment() {
- taskTabFragment = WeakReference(TaxiTaskTabFragment.newInstance())
+ showTaskFragment()
+ MogoStatusManager.getInstance()
+ .registerStatusChangedListener(
+ TAG, StatusDescriptor.TAXI_UNMANED_DRIVER_LINE_ROUTING_VERIFY_MODE
+ ) { descriptor, isTrue ->
+ if (StatusDescriptor.TAXI_UNMANED_DRIVER_LINE_ROUTING_VERIFY_MODE == descriptor) {
+ UiThreadHandler.post {
+ if (isTrue) {
+ showRoutingFragment()
+ } else {
+ showTaskFragment()
+ }
+ }
+ }
+ }
+ }
+
+ private fun showTaskFragment() {
val transaction: FragmentTransaction = childFragmentManager.beginTransaction()
- //默认显示OCHTaxiServerOrdersFragment
- taskTabFragment?.get()?.let {
- transaction.add(R.id.fragment_container, it).show(
- it
- )
+ if (routingVerifyFragment?.get()?.isVisible == true) {
+ routingVerifyFragment?.get()?.also {
+ transaction.hide(it)
+ }
+ }
+ if (taskTabFragment?.get() == null) {
+ taskTabFragment = WeakReference(TaxiTaskTabFragment.newInstance())
+ }
+ taskTabFragment?.get()?.also {
+ transaction.replace(R.id.fragment_container, it).show(it)
+ transaction.commitAllowingStateLoss()
+ }
+ }
+
+ private fun showRoutingFragment() {
+ val transaction: FragmentTransaction = childFragmentManager.beginTransaction()
+ if (taskTabFragment?.get()?.isVisible == true) {
+ taskTabFragment?.get()?.also {
+ transaction.hide(it)
+ }
+ }
+ if (routingVerifyFragment?.get() == null) {
+ routingVerifyFragment = WeakReference(TaxiRoutingFragment.newInstance())
+ }
+ routingVerifyFragment?.get()?.also {
+ transaction.replace(R.id.fragment_container, it).show(it)
+ transaction.commitAllowingStateLoss()
}
- transaction.commitAllowingStateLoss()
}
override fun createPresenter(): TaxiPresenter {
diff --git a/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineActivity.kt b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineActivity.kt
new file mode 100644
index 0000000000..a23d982b90
--- /dev/null
+++ b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineActivity.kt
@@ -0,0 +1,137 @@
+package com.mogo.och.taxi.ui.routing
+
+import android.graphics.Point
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.mogo.eagle.core.utilcode.mogo.view.SpacesItemDecoration
+import com.mogo.eagle.core.utilcode.util.ToastUtils
+import com.mogo.och.taxi.R
+import com.mogo.och.taxi.bean.TaxiRoutingQueryLineResponse
+import kotlinx.android.synthetic.main.routing_choose_line_activity.btnChooseLineSubmit
+import kotlinx.android.synthetic.main.routing_choose_line_activity.btnClose
+import kotlinx.android.synthetic.main.routing_choose_line_activity.chooseLineListView
+import kotlinx.android.synthetic.main.routing_no_data_common_view.noDataContainer
+import kotlinx.android.synthetic.main.taxi_debug_order.currentLineId
+
+class TaxiRoutingChooseLineActivity : AppCompatActivity() {
+
+ companion object {
+ const val TAG = "TaxiRoutingChooseLineActivity"
+ }
+
+ private lateinit var mChooseLineListAdapter: TaxiRoutingChooseLineAdapter
+ private lateinit var mLinearLayoutManager: LinearLayoutManager
+ private val mRoutingLineList: MutableList = ArrayList()
+ private var mCurrentChoosedLineId: Int? = -1
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.routing_choose_line_activity)
+ initWindowParams()
+ initView()
+ loadData()
+ }
+
+ private fun initWindowParams() {
+ val window = window
+ val params = window.attributes
+ val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
+ val point = Point()
+ windowManager.defaultDisplay.getSize(point) //用于获取屏幕高度
+ params.width = (point.x * 0.375).toInt()
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT
+ window.attributes = params
+ window.setGravity(Gravity.START or Gravity.BOTTOM)
+ }
+
+ private fun initView() {
+ mLinearLayoutManager = LinearLayoutManager(this)
+ chooseLineListView.layoutManager = mLinearLayoutManager
+ chooseLineListView.itemAnimator =
+ TaxiRoutingChooseLineItemOpenAnimator()
+ mChooseLineListAdapter = TaxiRoutingChooseLineAdapter(applicationContext, mRoutingLineList)
+ chooseLineListView.addItemDecoration(SpacesItemDecoration(4))
+ chooseLineListView.adapter = mChooseLineListAdapter
+ //设置item 点击事件
+ mChooseLineListAdapter.setOnLineItemClickListener(object :
+ TaxiRoutingChooseLineAdapter.OnChooseLineItemClickListener {
+ override fun onItemClick(position: Int, close: Boolean) {
+ mCurrentChoosedLineId = mRoutingLineList[position].lineId
+ }
+ })
+
+ btnClose.setOnClickListener {
+ finish()
+ }
+ btnChooseLineSubmit.setOnClickListener {
+ if (mCurrentChoosedLineId == -1) {
+ ToastUtils.showLong("请先选择任务")
+ return@setOnClickListener
+ }
+ ToastUtils.showLong("当前选择的路线LineId:$currentLineId")
+ }
+ }
+
+ private fun showEmptyView() {
+ chooseLineListView.visibility = View.GONE
+ btnChooseLineSubmit.visibility = View.GONE
+ noDataContainer.visibility = View.VISIBLE
+ }
+
+ private fun showRecyclerView() {
+ chooseLineListView.visibility = View.VISIBLE
+ btnChooseLineSubmit.visibility = View.VISIBLE
+ noDataContainer.visibility = View.GONE
+ }
+
+ /**
+ * 初始化数据
+ */
+ private fun loadData() {
+ //TODO
+ for (i in 1..10) {
+ val result = TaxiRoutingQueryLineResponse.Result()
+ result.lineId = i
+ result.name = "路线名称$i"
+ result.todayVerifyNum = 2
+ result.historyVerifyTotalUsableNum = 5
+ result.historyVerifyTotalNotUsableNum = 1
+ result.isChoosed = false
+ mRoutingLineList.add(result)
+ }
+ if (mRoutingLineList.isEmpty()) {
+ showEmptyView()
+ } else {
+ showRecyclerView()
+ mChooseLineListAdapter.notifyDataSetChanged()
+ }
+ }
+
+ fun onRoutingLineDataChanged(data: TaxiRoutingQueryLineResponse?) {
+ if (data?.data?.isNotEmpty() == true) {
+ showRecyclerView()
+ mRoutingLineList.clear()
+ mRoutingLineList.addAll(data.data)
+ mChooseLineListAdapter.notifyDataSetChanged()
+ } else {
+ showEmptyView()
+ }
+ }
+
+ fun onChangeLineIdSuccess() {
+ //ToastUtils.showLong(resources.getString(R.string.bus_change_line_commit_tip_s))
+ //mPresenter?.queryBusRoutes()
+ mChooseLineListAdapter.setOnLineItemClickListener(null)
+ //mPresenter?.removeListener()
+ finish()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineAdapter.kt b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineAdapter.kt
new file mode 100644
index 0000000000..ae513a83d3
--- /dev/null
+++ b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineAdapter.kt
@@ -0,0 +1,80 @@
+package com.mogo.och.taxi.ui.routing
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.recyclerview.widget.RecyclerView
+import com.mogo.och.taxi.R
+import com.mogo.och.taxi.bean.TaxiRoutingQueryLineResponse
+
+class TaxiRoutingChooseLineAdapter(
+ private val mContext: Context,
+ private val mData: List
+) : RecyclerView.Adapter() {
+
+ companion object {
+ const val TAG = "TaxiRoutingChooseLineAdapter"
+ }
+
+ private var mItemClickListener: OnChooseLineItemClickListener? = null
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SwitchLineViewHolder {
+ val view = LayoutInflater.from(mContext).inflate(
+ R.layout.routing_choose_line_list_item, parent, false
+ )
+ return SwitchLineViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: SwitchLineViewHolder, position: Int) {
+ val currentPosition = holder.bindingAdapterPosition
+ val data = mData[currentPosition]
+ holder.lineNameTextView.text = data.name
+ holder.todayVerifyNumTextView.text = "本车今日已验证:${data.todayVerifyNum}次"
+ holder.historyVerifyNumTextView.text =
+ "路线累计反馈${data.historyVerifyTotalUsableNum}可用,${data.historyVerifyTotalNotUsableNum}不可用"
+ if (data.isChoosed) {
+ holder.itemView.setBackgroundResource(R.drawable.routing_choose_line_shape_select_line_item_bg_selected)
+ } else {
+ holder.itemView.setBackgroundResource(R.drawable.routing_choose_line_shape_select_line_item_bg_normal)
+ }
+
+ //设置item点击事件
+ holder.itemView.setOnClickListener {
+ mData.forEachIndexed { index, result ->
+ if (result.isChoosed) {
+ result.isChoosed = false
+
+ }
+ }
+ mData[currentPosition].isChoosed = true
+ notifyItemChanged(currentPosition)
+ mItemClickListener?.onItemClick(currentPosition, false)
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return mData.size
+ }
+
+ fun setOnLineItemClickListener(itemClickListener: OnChooseLineItemClickListener?) {
+ mItemClickListener = itemClickListener
+ }
+
+ interface OnChooseLineItemClickListener {
+ fun onItemClick(position: Int, close: Boolean)
+ }
+
+ class SwitchLineViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val lineNameTextView: AppCompatTextView//线路名称
+ val todayVerifyNumTextView: AppCompatTextView //本车今天验证次数
+ val historyVerifyNumTextView: AppCompatTextView //路线累计验证次数
+
+ init {
+ lineNameTextView = itemView.findViewById(R.id.switchLineNameTextView)
+ todayVerifyNumTextView = itemView.findViewById(R.id.todayVerifyNumTextView)
+ historyVerifyNumTextView = itemView.findViewById(R.id.historyVerifyNumTextView)
+ }
+ }
+}
\ No newline at end of file
diff --git a/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineItemOpenAnimator.java b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineItemOpenAnimator.java
new file mode 100644
index 0000000000..323ec5e83a
--- /dev/null
+++ b/OCH/taxi/unmanned-driver/src/main/java/com/mogo/och/taxi/ui/routing/TaxiRoutingChooseLineItemOpenAnimator.java
@@ -0,0 +1,641 @@
+package com.mogo.och.taxi.ui.routing;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This implementation of {@link RecyclerView.ItemAnimator} provides basic
+ * animations on remove, add, and move events that happen to the items in
+ * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
+ *
+ * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
+ */
+public class TaxiRoutingChooseLineItemOpenAnimator extends DefaultItemAnimator {
+ private static final boolean DEBUG = false;
+ private static TimeInterpolator sDefaultInterpolator;
+ private final ArrayList mPendingRemovals = new ArrayList<>();
+ private final ArrayList mPendingAdditions = new ArrayList<>();
+ private final ArrayList mPendingMoves = new ArrayList<>();
+ private final ArrayList mPendingChanges = new ArrayList<>();
+ private final ArrayList> mAdditionsList = new ArrayList<>();
+ private final ArrayList> mMovesList = new ArrayList<>();
+ private final ArrayList> mChangesList = new ArrayList<>();
+ private final ArrayList mAddAnimations = new ArrayList<>();
+ private final ArrayList mMoveAnimations = new ArrayList<>();
+ private final ArrayList mRemoveAnimations = new ArrayList<>();
+ private final ArrayList mChangeAnimations = new ArrayList<>();
+
+ private static class MoveInfo {
+ public RecyclerView.ViewHolder holder;
+ public int fromX, fromY, toX, toY;
+
+ MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+ this.holder = holder;
+ this.fromX = fromX;
+ this.fromY = fromY;
+ this.toX = toX;
+ this.toY = toY;
+ }
+ }
+
+ private static class ChangeInfo {
+ public RecyclerView.ViewHolder oldHolder, newHolder;
+ public int fromX, fromY, toX, toY;
+
+ private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
+ this.oldHolder = oldHolder;
+ this.newHolder = newHolder;
+ }
+
+ ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ this(oldHolder, newHolder);
+ this.fromX = fromX;
+ this.fromY = fromY;
+ this.toX = toX;
+ this.toY = toY;
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeInfo{"
+ + "oldHolder=" + oldHolder
+ + ", newHolder=" + newHolder
+ + ", fromX=" + fromX
+ + ", fromY=" + fromY
+ + ", toX=" + toX
+ + ", toY=" + toY
+ + '}';
+ }
+ }
+
+ @Override
+ public void runPendingAnimations() {
+ boolean removalsPending = !mPendingRemovals.isEmpty();
+ boolean movesPending = !mPendingMoves.isEmpty();
+ boolean changesPending = !mPendingChanges.isEmpty();
+ boolean additionsPending = !mPendingAdditions.isEmpty();
+ if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
+ // nothing to animate
+ return;
+ }
+ // First, remove stuff
+ for (RecyclerView.ViewHolder holder : mPendingRemovals) {
+ animateRemoveImpl(holder);
+ }
+ mPendingRemovals.clear();
+ // Next, move stuff
+ if (movesPending) {
+ final ArrayList moves = new ArrayList<>(mPendingMoves);
+ mMovesList.add(moves);
+ mPendingMoves.clear();
+ Runnable mover = () -> {
+ for (MoveInfo moveInfo : moves) {
+ animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
+ moveInfo.toX, moveInfo.toY);
+ }
+ moves.clear();
+ mMovesList.remove(moves);
+ };
+ if (removalsPending) {
+ View view = moves.get(0).holder.itemView;
+ ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
+ } else {
+ mover.run();
+ }
+ }
+ // Next, change stuff, to run in parallel with move animations
+ if (changesPending) {
+ final ArrayList changes = new ArrayList<>(mPendingChanges);
+ mChangesList.add(changes);
+ mPendingChanges.clear();
+ Runnable changer = () -> {
+ for (ChangeInfo change : changes) {
+ animateChangeImpl(change);
+ }
+ changes.clear();
+ mChangesList.remove(changes);
+ };
+ if (removalsPending) {
+ RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
+ ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
+ } else {
+ changer.run();
+ }
+ }
+ // Next, add stuff
+ if (additionsPending) {
+ final ArrayList additions = new ArrayList<>(mPendingAdditions);
+ mAdditionsList.add(additions);
+ mPendingAdditions.clear();
+ Runnable adder = () -> {
+ for (RecyclerView.ViewHolder holder : additions) {
+ animateAddImpl(holder);
+ }
+ additions.clear();
+ mAdditionsList.remove(additions);
+ };
+ if (removalsPending || movesPending || changesPending) {
+ long removeDuration = removalsPending ? getRemoveDuration() : 0;
+ long moveDuration = movesPending ? getMoveDuration() : 0;
+ long changeDuration = changesPending ? getChangeDuration() : 0;
+ long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
+ View view = additions.get(0).itemView;
+ ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
+ } else {
+ adder.run();
+ }
+ }
+ }
+
+ @Override
+ public boolean animateRemove(final RecyclerView.ViewHolder holder) {
+ resetAnimation(holder);
+ mPendingRemovals.add(holder);
+ return true;
+ }
+
+ private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
+ final View view = holder.itemView;
+ final ViewPropertyAnimator animation = view.animate();
+ mRemoveAnimations.add(holder);
+ animation.setDuration(getRemoveDuration()).alpha(0).setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchRemoveStarting(holder);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ view.setAlpha(1);
+ dispatchRemoveFinished(holder);
+ mRemoveAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateAdd(final RecyclerView.ViewHolder holder) {
+ resetAnimation(holder);
+ holder.itemView.setAlpha(0);
+ mPendingAdditions.add(holder);
+ return true;
+ }
+
+ void animateAddImpl(final RecyclerView.ViewHolder holder) {
+ final View view = holder.itemView;
+ final ViewPropertyAnimator animation = view.animate();
+ mAddAnimations.add(holder);
+ animation.alpha(1).setDuration(getAddDuration())
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchAddStarting(holder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ view.setAlpha(1);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ dispatchAddFinished(holder);
+ mAddAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
+ int toX, int toY) {
+ final View view = holder.itemView;
+ fromX += (int) holder.itemView.getTranslationX();
+ fromY += (int) holder.itemView.getTranslationY();
+ resetAnimation(holder);
+ int deltaX = toX - fromX;
+ int deltaY = toY - fromY;
+ if (deltaX == 0 && deltaY == 0) {
+ dispatchMoveFinished(holder);
+ return false;
+ }
+ if (deltaX != 0) {
+ view.setTranslationX(-deltaX);
+ }
+ if (deltaY != 0) {
+ view.setTranslationY(-deltaY);
+ }
+ mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+ final View view = holder.itemView;
+ final int deltaX = toX - fromX;
+ final int deltaY = toY - fromY;
+ if (deltaX != 0) {
+ view.animate().translationX(0);
+ }
+ if (deltaY != 0) {
+ view.animate().translationY(0);
+ }
+ // TODO: make EndActions end listeners instead, since end actions aren't called when
+ // vpas are canceled (and can't end them. why?)
+ // need listener functionality in VPACompat for this. Ick.
+ final ViewPropertyAnimator animation = view.animate();
+ mMoveAnimations.add(holder);
+ animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchMoveStarting(holder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ if (deltaX != 0) {
+ view.setTranslationX(0);
+ }
+ if (deltaY != 0) {
+ view.setTranslationY(0);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ dispatchMoveFinished(holder);
+ mMoveAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ if (oldHolder == newHolder) {
+ // Don't know how to run change animations when the same view holder is re-used.
+ // run a move animation to handle position changes.
+ return animateMove(oldHolder, fromX, fromY, toX, toY);
+ }
+ final float prevTranslationX = oldHolder.itemView.getTranslationX();
+ final float prevTranslationY = oldHolder.itemView.getTranslationY();
+ final float prevAlpha = oldHolder.itemView.getAlpha();
+ resetAnimation(oldHolder);
+ int deltaX = (int) (toX - fromX - prevTranslationX);
+ int deltaY = (int) (toY - fromY - prevTranslationY);
+ // recover prev translation state after ending animation
+ oldHolder.itemView.setTranslationX(prevTranslationX);
+ oldHolder.itemView.setTranslationY(prevTranslationY);
+ oldHolder.itemView.setAlpha(prevAlpha);
+ if (newHolder != null) {
+ // carry over translation values
+ resetAnimation(newHolder);
+ newHolder.itemView.setTranslationX(-deltaX);
+ newHolder.itemView.setTranslationY(-deltaY);
+ newHolder.itemView.setAlpha(0);
+ }
+ mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ void animateChangeImpl(final ChangeInfo changeInfo) {
+ final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
+ final View view = holder == null ? null : holder.itemView;
+ final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
+ final View newView = newHolder != null ? newHolder.itemView : null;
+ if (view != null) {
+ final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
+ getChangeDuration());
+ mChangeAnimations.add(changeInfo.oldHolder);
+ oldViewAnim.translationX((float) (changeInfo.toX - changeInfo.fromX));
+ oldViewAnim.translationY((float) (changeInfo.toY - changeInfo.fromY));
+ oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchChangeStarting(changeInfo.oldHolder, true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ oldViewAnim.setListener(null);
+ view.setAlpha(1);
+ view.setTranslationX(0);
+ view.setTranslationY(0);
+ dispatchChangeFinished(changeInfo.oldHolder, true);
+ mChangeAnimations.remove(changeInfo.oldHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+ if (newView != null) {
+ final ViewPropertyAnimator newViewAnimation = newView.animate();
+ mChangeAnimations.add(changeInfo.newHolder);
+ newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
+ .alpha(1).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchChangeStarting(changeInfo.newHolder, false);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ newViewAnimation.setListener(null);
+ newView.setAlpha(1);
+ newView.setTranslationX(0);
+ newView.setTranslationY(0);
+ dispatchChangeFinished(changeInfo.newHolder, false);
+ mChangeAnimations.remove(changeInfo.newHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+ }
+
+ private void endChangeAnimation(List infoList, RecyclerView.ViewHolder item) {
+ for (int i = infoList.size() - 1; i >= 0; i--) {
+ ChangeInfo changeInfo = infoList.get(i);
+ if (endChangeAnimationIfNecessary(changeInfo, item)) {
+ if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
+ infoList.remove(changeInfo);
+ }
+ }
+ }
+ }
+
+ private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
+ if (changeInfo.oldHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
+ }
+ if (changeInfo.newHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
+ }
+ }
+
+ private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
+ boolean oldItem = false;
+ if (changeInfo.newHolder == item) {
+ changeInfo.newHolder = null;
+ } else if (changeInfo.oldHolder == item) {
+ changeInfo.oldHolder = null;
+ oldItem = true;
+ } else {
+ return false;
+ }
+ item.itemView.setAlpha(1);
+ item.itemView.setTranslationX(0);
+ item.itemView.setTranslationY(0);
+ dispatchChangeFinished(item, oldItem);
+ return true;
+ }
+
+ @Override
+ public void endAnimation(RecyclerView.ViewHolder item) {
+ final View view = item.itemView;
+ // this will trigger end callback which should set properties to their target values.
+ view.animate().cancel();
+ // TODO if some other animations are chained to end, how do we cancel them as well?
+ for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
+ MoveInfo moveInfo = mPendingMoves.get(i);
+ if (moveInfo.holder == item) {
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item);
+ mPendingMoves.remove(i);
+ }
+ }
+ endChangeAnimation(mPendingChanges, item);
+ if (mPendingRemovals.remove(item)) {
+ view.setAlpha(1);
+ dispatchRemoveFinished(item);
+ }
+ if (mPendingAdditions.remove(item)) {
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ }
+
+ for (int i = mChangesList.size() - 1; i >= 0; i--) {
+ ArrayList changes = mChangesList.get(i);
+ endChangeAnimation(changes, item);
+ if (changes.isEmpty()) {
+ mChangesList.remove(i);
+ }
+ }
+ for (int i = mMovesList.size() - 1; i >= 0; i--) {
+ ArrayList moves = mMovesList.get(i);
+ for (int j = moves.size() - 1; j >= 0; j--) {
+ MoveInfo moveInfo = moves.get(j);
+ if (moveInfo.holder == item) {
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ mMovesList.remove(i);
+ }
+ break;
+ }
+ }
+ }
+ for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
+ ArrayList additions = mAdditionsList.get(i);
+ if (additions.remove(item)) {
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ if (additions.isEmpty()) {
+ mAdditionsList.remove(i);
+ }
+ }
+ }
+
+ // animations should be ended by the cancel above.
+ //noinspection Pointless BooleanExpression,ConstantConditions
+ if (mRemoveAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mRemoveAnimations list");
+ }
+
+ //noinspection Pointless BooleanExpression,ConstantConditions
+ if (mAddAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mAddAnimations list");
+ }
+
+ //noinspection Pointless BooleanExpression,ConstantConditions
+ if (mChangeAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mChangeAnimations list");
+ }
+
+ //noinspection Pointless BooleanExpression,ConstantConditions
+ if (mMoveAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mMoveAnimations list");
+ }
+ dispatchFinishedWhenDone();
+ }
+
+ private void resetAnimation(RecyclerView.ViewHolder holder) {
+ if (sDefaultInterpolator == null) {
+ sDefaultInterpolator = new ValueAnimator().getInterpolator();
+ }
+ holder.itemView.animate().setInterpolator(sDefaultInterpolator);
+ endAnimation(holder);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return (!mPendingAdditions.isEmpty()
+ || !mPendingChanges.isEmpty()
+ || !mPendingMoves.isEmpty()
+ || !mPendingRemovals.isEmpty()
+ || !mMoveAnimations.isEmpty()
+ || !mRemoveAnimations.isEmpty()
+ || !mAddAnimations.isEmpty()
+ || !mChangeAnimations.isEmpty()
+ || !mMovesList.isEmpty()
+ || !mAdditionsList.isEmpty()
+ || !mChangesList.isEmpty());
+ }
+
+ /**
+ * Check the state of currently pending and running animations. If there are none
+ * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
+ * listeners.
+ */
+ void dispatchFinishedWhenDone() {
+ if (!isRunning()) {
+ dispatchAnimationsFinished();
+ }
+ }
+
+ @Override
+ public void endAnimations() {
+ int count = mPendingMoves.size();
+ for (int i = count - 1; i >= 0; i--) {
+ MoveInfo item = mPendingMoves.get(i);
+ View view = item.holder.itemView;
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item.holder);
+ mPendingMoves.remove(i);
+ }
+ count = mPendingRemovals.size();
+ for (int i = count - 1; i >= 0; i--) {
+ RecyclerView.ViewHolder item = mPendingRemovals.get(i);
+ dispatchRemoveFinished(item);
+ mPendingRemovals.remove(i);
+ }
+ count = mPendingAdditions.size();
+ for (int i = count - 1; i >= 0; i--) {
+ RecyclerView.ViewHolder item = mPendingAdditions.get(i);
+ item.itemView.setAlpha(1);
+ dispatchAddFinished(item);
+ mPendingAdditions.remove(i);
+ }
+ count = mPendingChanges.size();
+ for (int i = count - 1; i >= 0; i--) {
+ endChangeAnimationIfNecessary(mPendingChanges.get(i));
+ }
+ mPendingChanges.clear();
+ if (!isRunning()) {
+ return;
+ }
+
+ int listCount = mMovesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList moves = mMovesList.get(i);
+ count = moves.size();
+ for (int j = count - 1; j >= 0; j--) {
+ MoveInfo moveInfo = moves.get(j);
+ RecyclerView.ViewHolder item = moveInfo.holder;
+ View view = item.itemView;
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(moveInfo.holder);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ mMovesList.remove(moves);
+ }
+ }
+ }
+ listCount = mAdditionsList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList additions = mAdditionsList.get(i);
+ count = additions.size();
+ for (int j = count - 1; j >= 0; j--) {
+ RecyclerView.ViewHolder item = additions.get(j);
+ View view = item.itemView;
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ additions.remove(j);
+ if (additions.isEmpty()) {
+ mAdditionsList.remove(additions);
+ }
+ }
+ }
+ listCount = mChangesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList changes = mChangesList.get(i);
+ count = changes.size();
+ for (int j = count - 1; j >= 0; j--) {
+ endChangeAnimationIfNecessary(changes.get(j));
+ if (changes.isEmpty()) {
+ mChangesList.remove(changes);
+ }
+ }
+ }
+
+ cancelAll(mRemoveAnimations);
+ cancelAll(mMoveAnimations);
+ cancelAll(mAddAnimations);
+ cancelAll(mChangeAnimations);
+
+ dispatchAnimationsFinished();
+ }
+
+ void cancelAll(List viewHolders) {
+ for (int i = viewHolders.size() - 1; i >= 0; i--) {
+ viewHolders.get(i).itemView.animate().cancel();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * If the payload list is not empty, DefaultItemAnimator returns true.
+ * When this is the case:
+ *
+ *
If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both
+ * ViewHolder arguments will be the same instance.
+ *
+ *
+ * If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
+ * then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and
+ * run a move animation instead.
+ *
+ *
+ */
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
+ @NonNull List