[m1]
[1.0.0] [视频播放]
@@ -76,6 +76,7 @@ dependencies {
|
||||
implementation project(":OCH:mogo-och-common-module")
|
||||
compileOnly project(":libraries:mogo-map")
|
||||
compileOnly project(':libraries:mogo-adas')
|
||||
implementation project(':core:mogo-core-res')
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.mogo.och.bus.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,9 +1,20 @@
|
||||
package com.mogo.och.bus.passenger.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mogo.commons.mvp.MvpFragment
|
||||
import com.mogo.och.bus.passenger.R
|
||||
import com.mogo.och.bus.passenger.bean.TaxiPassengerVideoPlay
|
||||
import com.mogo.och.bus.passenger.presenter.BusPassengerFunctionVideoPresenter
|
||||
import com.mogo.och.bus.passenger.ui.adapter.RecyclerVideoAdapter
|
||||
import com.mogo.och.bus.passenger.ui.layoutmanage.CarouselLayoutManager
|
||||
import com.mogo.och.bus.passenger.ui.layoutmanage.CarouselZoomPostLayoutListener
|
||||
import com.mogo.och.bus.passenger.ui.layoutmanage.CenterScrollListener
|
||||
import com.mogo.och.bus.passenger.view.ConsultVideoPlayer
|
||||
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
|
||||
import kotlinx.android.synthetic.m1.bus_p_function_video_fragment.*
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* @author: yangyakun
|
||||
@@ -11,6 +22,9 @@ import com.mogo.och.bus.passenger.presenter.BusPassengerFunctionVideoPresenter
|
||||
*/
|
||||
class BusPassengerFunctionVideoFragment :
|
||||
MvpFragment<BusPassengerFunctionVideoFragment?, BusPassengerFunctionVideoPresenter?>() {
|
||||
|
||||
private val arrayListOf = ArrayList<TaxiPassengerVideoPlay>()
|
||||
|
||||
override fun getLayoutId(): Int {
|
||||
return R.layout.bus_p_function_video_fragment
|
||||
}
|
||||
@@ -20,7 +34,108 @@ class BusPassengerFunctionVideoFragment :
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
initConsultData()
|
||||
val carouselLayoutManager = CarouselLayoutManager(CarouselLayoutManager.HORIZONTAL, true)
|
||||
carouselLayoutManager.setPostLayoutListener(CarouselZoomPostLayoutListener())
|
||||
carouselLayoutManager.maxVisibleItems = 1
|
||||
rvVideoPlaylist.addOnScrollListener(object : CenterScrollListener() {
|
||||
var prePlayerPosition = 0
|
||||
override fun pageSelect(recyclerView: RecyclerView?, newState: Int) {
|
||||
//播放视频
|
||||
val (centerItemPosition: kotlin.Int, player) = getPlayer(carouselLayoutManager)
|
||||
if (player is ConsultVideoPlayer) {
|
||||
if (prePlayerPosition != centerItemPosition) {
|
||||
if (player.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
|
||||
player.onVideoReset()
|
||||
}
|
||||
val playerHolder =
|
||||
carouselLayoutManager.findViewByPosition(prePlayerPosition)
|
||||
val prePlayer =
|
||||
playerHolder?.findViewById<ConsultVideoPlayer>(R.id.video_item_player)
|
||||
prePlayer?.onVideoReset()
|
||||
val taxiPassengerVideoPlay = arrayListOf[centerItemPosition]
|
||||
setBackageAndPlayNext(taxiPassengerVideoPlay)
|
||||
} else {
|
||||
player.onVideoResume(false)
|
||||
}
|
||||
}
|
||||
prePlayerPosition = centerItemPosition
|
||||
}
|
||||
|
||||
override fun pageStop() {
|
||||
val (_: kotlin.Int, player) = getPlayer(carouselLayoutManager)
|
||||
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 - 1
|
||||
} else {
|
||||
currentIndex -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
val recyclerVideoAdapter = RecyclerVideoAdapter(requireContext(), arrayListOf, rvVideoPlaylist)
|
||||
recyclerVideoAdapter.setOnThumbImageClilckListener {
|
||||
val (_: kotlin.Int, player) = getPlayer(carouselLayoutManager)
|
||||
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 getPlayer(carouselLayoutManager: CarouselLayoutManager): Pair<Int, ConsultVideoPlayer?> {
|
||||
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
|
||||
val playerHolder = carouselLayoutManager.findViewByPosition(centerItemPosition)
|
||||
val player = playerHolder?.findViewById<ConsultVideoPlayer>(R.id.video_item_player)
|
||||
return Pair(centerItemPosition, player)
|
||||
}
|
||||
|
||||
private fun setBackageAndPlayNext(taxiPassengerVideoPlay: TaxiPassengerVideoPlay) {
|
||||
// 设置背景图片
|
||||
}
|
||||
|
||||
private fun initConsultData() {
|
||||
arrayListOf.clear()
|
||||
arrayListOf.add(
|
||||
TaxiPassengerVideoPlay(
|
||||
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708596763/全车型混剪增加红旗车队.m4v",
|
||||
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969511280/车队.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/1655708499497/大运会合作解说版.m4v",
|
||||
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969536177/大运会.png",
|
||||
"蘑菇车联牵手成都大运会"
|
||||
)
|
||||
)
|
||||
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",
|
||||
"多视角体验蘑菇车联自动驾驶"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun createPresenter(): BusPassengerFunctionVideoPresenter {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.mogo.och.bus.passenger.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.mogo.och.bus.passenger.R;
|
||||
import com.mogo.och.bus.passenger.view.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,130 @@
|
||||
package com.mogo.och.bus.passenger.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
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.bus.passenger.R;
|
||||
import com.mogo.och.bus.passenger.bean.TaxiPassengerVideoPlay;
|
||||
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.jessyan.autosize.AutoSizeCompat;
|
||||
|
||||
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;
|
||||
private RecyclerView recyclerView;
|
||||
|
||||
public OnThumbImageClilckListener getOnThumbImageClilckListener() {
|
||||
return onThumbImageClilckListener;
|
||||
}
|
||||
|
||||
public void setOnThumbImageClilckListener(OnThumbImageClilckListener onThumbImageClilckListener) {
|
||||
this.onThumbImageClilckListener = onThumbImageClilckListener;
|
||||
}
|
||||
|
||||
public RecyclerVideoAdapter(Context context, List<TaxiPassengerVideoPlay> itemDataList,RecyclerView recyclerView) {
|
||||
this.itemDataList = itemDataList;
|
||||
this.context = context;
|
||||
this.recyclerView = recyclerView;
|
||||
}
|
||||
|
||||
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);
|
||||
AutoSizeCompat.autoConvertDensityOfGlobal(holder.itemView.getResources());
|
||||
holder.gsyVideoOptionBuilder
|
||||
.setEnlargeImageRes(R.drawable.taxi_p_change_full)
|
||||
.setUrl(taxiPassengerVideoPlay.getUrl())
|
||||
.setCacheWithPlay(true)
|
||||
.setPlayTag(taxiPassengerVideoPlay.getImageUrl()+position)
|
||||
.setThumbPlay(false)
|
||||
.build(holder.gsyVideoPlayer);
|
||||
holder.gsyVideoPlayer.getTitleTextView().setText(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.getThumbImageViewLayout().setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if(onThumbImageClilckListener!=null){
|
||||
onThumbImageClilckListener.onDxChanged(holder.getAbsoluteAdapterPosition());
|
||||
}
|
||||
}
|
||||
});
|
||||
holder.gsyVideoPlayer.setVideoAllCallBack(new GSYSampleCallBack(){
|
||||
@Override
|
||||
public void onAutoComplete(String url, Object... objects) {
|
||||
holder.gsyVideoPlayer.onVideoReset();
|
||||
if(holder.getAbsoluteAdapterPosition()==getItemCount()-1){
|
||||
recyclerView.smoothScrollToPosition(0);
|
||||
}else {
|
||||
recyclerView.smoothScrollToPosition(holder.getAbsoluteAdapterPosition()+1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickBlank(String url, Object... objects) {
|
||||
super.onClickBlank(url, objects);
|
||||
recyclerView.smoothScrollToPosition(holder.getAbsoluteAdapterPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayError(String url, Object... objects) {
|
||||
ToastUtils.showLong("哎呀,出错了,看看其他视频吧");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickStartError(String url, Object... objects) {
|
||||
ToastUtils.showLong("哎呀,出错了,看看其他视频吧");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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,970 @@
|
||||
package com.mogo.och.bus.passenger.ui.layoutmanage;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.os.Build;
|
||||
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);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
view.setTransitionAlpha(transformation.mAlpha);
|
||||
}else {
|
||||
view.setAlpha(transformation.mAlpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中心项目的当前滚动位置。如果是循环布局,则该值可以在任何范围内。如果不是,那么它在[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,40 @@
|
||||
package com.mogo.och.bus.passenger.ui.layoutmanage;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Implementation of {@link CarouselLayoutManager.PostLayoutListener} that makes interesting scaling of items. <br />
|
||||
* We are trying to make items scaling quicker for closer items for center and slower for when they are far away.<br />
|
||||
* Tis implementation uses atan function for this purpose.
|
||||
*/
|
||||
public class CarouselZoomPostLayoutListener extends CarouselLayoutManager.PostLayoutListener {
|
||||
|
||||
private final float mScaleMultiplier;
|
||||
|
||||
public CarouselZoomPostLayoutListener() {
|
||||
this(0.21f);
|
||||
}
|
||||
|
||||
public CarouselZoomPostLayoutListener(final float scaleMultiplier) {
|
||||
mScaleMultiplier = scaleMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemTransformation transformChild(@NonNull final View child, final float itemPositionToCenterDiff, final int orientation) {
|
||||
float scale = 1.0f - mScaleMultiplier * Math.abs(itemPositionToCenterDiff);
|
||||
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)/8;
|
||||
translateX = Math.signum(itemPositionToCenterDiff) * translateXGeneral;
|
||||
translateY = 0;
|
||||
}
|
||||
return new ItemTransformation(scale,scale, scale, 0, translateY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.mogo.och.bus.passenger.ui.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,18 @@
|
||||
package com.mogo.och.bus.passenger.ui.layoutmanage;
|
||||
|
||||
public class ItemTransformation {
|
||||
|
||||
final float mAlpha;
|
||||
final float mScaleX;
|
||||
final float mScaleY;
|
||||
final float mTranslationX;
|
||||
final float mTranslationY;
|
||||
|
||||
public ItemTransformation(final float alpha,final float scaleX, final float scaleY, final float translationX, final float translationY) {
|
||||
mScaleX = scaleX;
|
||||
mScaleY = scaleY;
|
||||
mTranslationX = translationX;
|
||||
mTranslationY = translationY;
|
||||
mAlpha = alpha;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.mogo.och.bus.passenger.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import com.mogo.och.bus.passenger.view.ConsultVideoPlayer
|
||||
import com.shuyu.gsyvideoplayer.GSYVideoManager
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* 视频全屏播放
|
||||
*
|
||||
* @author yangyakun
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object FullVideoUtils {
|
||||
private const val TAG = "OverlayViewUtils"
|
||||
private var windowManager: WindowManager? = 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 (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_OVERLAY
|
||||
// 没有边界限制,允许窗口扩展到屏幕外
|
||||
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(needReleas:Boolean) {
|
||||
if (!isShowing) {
|
||||
return
|
||||
}
|
||||
val consultVideoPlayer =
|
||||
lastOverlayView?.findViewById<ConsultVideoPlayer>(GSYVideoManager.FULLSCREEN_ID)
|
||||
consultVideoPlayer?.let {
|
||||
if(needReleas){
|
||||
it.onVideoReset()
|
||||
it.setVideoAllCallBack(null)
|
||||
it.smalllPlayer?.clearFullscreenLayout(it)
|
||||
}
|
||||
consultVideoPlayer.removeAllViews()
|
||||
}
|
||||
try {
|
||||
if (windowManager != null) {
|
||||
windowManager!!.removeViewImmediate(lastOverlayView)
|
||||
windowManager = null
|
||||
}
|
||||
if (lastOverlayView != null) {
|
||||
lastOverlayView = null
|
||||
}
|
||||
isShowing = false
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.mogo.och.bus.passenger.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
object ZoomDrawable {
|
||||
fun zoomDrawableImage(context: Context,@DrawableRes id:Int,scaleX:Float,scaleY:Float):Drawable{
|
||||
|
||||
val bitmap: Bitmap = BitmapFactory.decodeResource(context.resources, id)
|
||||
val bitmapWidth = bitmap.width
|
||||
val bitmapHeight = bitmap.height
|
||||
val matrix = Matrix()
|
||||
matrix.postScale(scaleX, scaleY)
|
||||
// 产生缩放后的Bitmap对象
|
||||
val resizeBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true)
|
||||
return BitmapDrawable(context.resources,resizeBitmap)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
package com.mogo.och.bus.passenger.view
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.mogo.eagle.core.utilcode.util.TimeTransformUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils
|
||||
import com.mogo.eagle.core.widget.media.video.TextureVideoViewOutlineProvider
|
||||
import com.mogo.och.bus.passenger.R
|
||||
import com.mogo.och.bus.passenger.utils.FullVideoUtils
|
||||
import com.mogo.och.bus.passenger.utils.ZoomDrawable
|
||||
import com.shuyu.gsyvideoplayer.listener.VideoAllCallBack
|
||||
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 me.jessyan.autosize.utils.AutoSizeUtils
|
||||
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 layoutBottom: ConstraintLayout
|
||||
private lateinit var vPpenLeft: View
|
||||
|
||||
private var fullVideoPlayer:ConsultVideoPlayer?=null
|
||||
var smalllPlayer:ConsultVideoPlayer?=null
|
||||
|
||||
private var currentTime = 0
|
||||
|
||||
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)
|
||||
vPpenLeft = findViewById(R.id.v_open_left)
|
||||
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_FULL)
|
||||
aivStartPlay.scaleX = 0.8f
|
||||
aivStartPlay.scaleY = 0.8f
|
||||
|
||||
mProgressBar.thumb = ZoomDrawable.zoomDrawableImage(context,R.drawable.bg_taxi_p_video_index,0.66f,0.66f)
|
||||
}
|
||||
|
||||
private fun addDrageAnchor(){
|
||||
vPpenLeft.visibility = VISIBLE
|
||||
layoutBottom.post {
|
||||
val layoutParams = layoutBottom.layoutParams as ConstraintLayout.LayoutParams
|
||||
layoutParams.height = AutoSizeUtils.dp2px(context,176f)
|
||||
layoutBottom.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
mTopContainer.post {
|
||||
// val layoutParams = mTopContainer.layoutParams as ConstraintLayout.LayoutParams
|
||||
// layoutParams.height = 320
|
||||
// mTopContainer.layoutParams = layoutParams
|
||||
val background = layoutBottom.background as GradientDrawable
|
||||
val x = arrayOf(12f, 12f,12f, 12f,12f, 12f,12f, 12f)
|
||||
background.cornerRadii = x.toFloatArray()
|
||||
layoutBottom.background = background
|
||||
|
||||
fullscreenButton.setPadding(92,0,92,0)
|
||||
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, AutoSizeUtils.dp2px(context,40f).toFloat())
|
||||
val layoutParams1 = titleTextView.layoutParams as ConstraintLayout.LayoutParams
|
||||
layoutParams1.marginStart = 80
|
||||
titleTextView.layoutParams = layoutParams1
|
||||
aivStartPlay.scaleX = 1f
|
||||
aivStartPlay.scaleY = 1f
|
||||
val drawable = ActivityCompat.getDrawable(context, R.drawable.bg_taxi_p_video_index)
|
||||
mProgressBar.thumb = drawable
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
mProgressBar.maxHeight = 6
|
||||
mProgressBar.minHeight = 6
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getLayoutId(): Int {
|
||||
return R.layout.taxi_p_video_show
|
||||
}
|
||||
|
||||
override fun updateStartImage() {
|
||||
when (mCurrentState) {
|
||||
GSYVideoView.CURRENT_STATE_PLAYING ->{
|
||||
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 setStateAndUi(state: Int) {
|
||||
super.setStateAndUi(state)
|
||||
if(state==CURRENT_STATE_PLAYING_BUFFERING_START){
|
||||
ToastUtils.showShort("加载中请稍等")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasWindowFocus)
|
||||
if(isIfCurrentIsFullscreen&&smalllPlayer!=null){
|
||||
if(hasWindowFocus){//获取焦点
|
||||
onVideoResume()
|
||||
}else{
|
||||
onVideoPause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun touchDoubleUp() {
|
||||
|
||||
}
|
||||
|
||||
override fun changeUiToNormal() {
|
||||
super.changeUiToNormal()
|
||||
setViewShowState(fullscreenButton, INVISIBLE)
|
||||
}
|
||||
|
||||
override fun changeUiToPlayingShow() {
|
||||
super.changeUiToPlayingShow()
|
||||
setViewShowState(fullscreenButton, VISIBLE)
|
||||
}
|
||||
|
||||
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(currentTime>=totalTime-3000){//
|
||||
this.currentTime = -1
|
||||
}else{
|
||||
this.currentTime = currentTime
|
||||
}
|
||||
if (progress != 0) {
|
||||
mProgressBar?.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
override fun showWifiDialog() {
|
||||
//直接播放,不显示WIFI对话框
|
||||
startPlayLogic()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
mProgressBar?.progress = 0
|
||||
fullVideoPlayer?.let {
|
||||
clearFullscreenLayout(it)
|
||||
}
|
||||
fullVideoPlayer = null
|
||||
if(!isIfCurrentIsFullscreen) {
|
||||
onVideoReset()
|
||||
setVideoAllCallBack(null)
|
||||
}
|
||||
dismissProgressDialog()
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
super.onClick(v)
|
||||
when (v?.id) {
|
||||
R.id.fullscreen -> {
|
||||
startWindowFullscreenOwn(context)
|
||||
// startWindowFullscreen(context)
|
||||
}
|
||||
R.id.aiv_start_play -> {
|
||||
if(currentState==GSYVideoView.CURRENT_STATE_PAUSE){
|
||||
onVideoResume(false)
|
||||
}else{
|
||||
if (mProgressBar==null) {
|
||||
startPlayLogic()
|
||||
}else {
|
||||
mProgressBar?.let {
|
||||
if(currentTime>0) {
|
||||
seekOnStart = currentTime.toLong()
|
||||
}
|
||||
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)
|
||||
mThumbImageViewLayout?.visibility = View.VISIBLE
|
||||
ToastUtils.showLong("哎呀,出错了,看看其他视频吧")
|
||||
currentTime = -1
|
||||
if(isIfCurrentIsFullscreen){
|
||||
smalllPlayer?.clearFullscreenLayout(this)
|
||||
smalllPlayer?.currentTime = -1
|
||||
FullVideoUtils.dismissOverlayView(false)
|
||||
}
|
||||
}
|
||||
|
||||
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){
|
||||
if(smalllPlayer!=null){
|
||||
smalllPlayer?.clearFullscreenLayout(this)
|
||||
}
|
||||
FullVideoUtils.dismissOverlayView(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
if (!mIfCurrentIsFullscreen) {
|
||||
this.outlineProvider = TextureVideoViewOutlineProvider(38F)
|
||||
this.clipToOutline = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun startWindowFullscreenOwn(context:Context){
|
||||
val gsyBaseVideoPlayer = startWindowFullscreen(context)
|
||||
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
|
||||
)
|
||||
}
|
||||
standardGSYVideoPlayer.titleTextView?.text = titleTextView.text
|
||||
}
|
||||
|
||||
private fun startWindowFullscreen(context:Context):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)
|
||||
gsyVideoPlayer.addDrageAnchor()
|
||||
cloneParams(this, gsyVideoPlayer)
|
||||
val frameLayout = FrameLayout(context)
|
||||
if (gsyVideoPlayer.fullscreenButton != null) {
|
||||
gsyVideoPlayer.fullscreenButton.setImageResource(R.drawable.taxi_p_change_normal)
|
||||
gsyVideoPlayer.start.setImageResource(R.drawable.notice_video_pause_big)
|
||||
gsyVideoPlayer.fullscreenButton.setOnClickListener { v ->
|
||||
if (mBackFromFullScreenListener == null) {
|
||||
clearFullscreenLayout(gsyVideoPlayer)
|
||||
FullVideoUtils.dismissOverlayView(false)
|
||||
} else {
|
||||
mBackFromFullScreenListener.onClick(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
gsyVideoPlayer.smalllPlayer = this
|
||||
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.WRAP_CONTENT
|
||||
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
lp.gravity = Gravity.BOTTOM
|
||||
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) {
|
||||
mVideoAllCallBack.onEnterFullscreen(mOriginUrl, mTitle, gsyVideoPlayer)
|
||||
}
|
||||
mIfCurrentIsFullscreen = true
|
||||
checkoutState()
|
||||
checkAutoFullWithSizeAndAdaptation(gsyVideoPlayer)
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun getStatusBarHeight(): Int{
|
||||
return Math.ceil((25 * context.resources.displayMetrics.density).toDouble()).toInt()
|
||||
}
|
||||
|
||||
fun clearFullscreenLayout(gsyVideoPlayer:ConsultVideoPlayer) {
|
||||
mIfCurrentIsFullscreen = false
|
||||
val delay = 100
|
||||
gsyVideoPlayer.smalllPlayer = null
|
||||
mInnerHandler.postDelayed({ resolveNormalVideoShow(gsyVideoPlayer) }, delay.toLong())
|
||||
}
|
||||
|
||||
private fun resolveNormalVideoShow(gsyVideoPlayer: GSYVideoPlayer) {
|
||||
mCurrentState = gsyVideoManager.lastState
|
||||
cloneParams(gsyVideoPlayer, this)
|
||||
gsyVideoManager.setListener(gsyVideoManager.lastListener())
|
||||
gsyVideoManager.setLastListener(null)
|
||||
gsyVideoPlayer.setVideoAllCallBack(null)
|
||||
setStateAndUi(mCurrentState)
|
||||
addTextureView()
|
||||
mSaveChangeViewTIme = System.currentTimeMillis()
|
||||
if (mVideoAllCallBack != null) {
|
||||
mVideoAllCallBack.onQuitFullscreen(mOriginUrl, mTitle, this)
|
||||
}
|
||||
mIfCurrentIsFullscreen = false
|
||||
if (fullscreenButton != null) {
|
||||
fullscreenButton.setImageResource(enlargeImageRes)
|
||||
}
|
||||
this.fullVideoPlayer = null
|
||||
}
|
||||
|
||||
fun getVideoAllCallBack(): VideoAllCallBack? {
|
||||
return mVideoAllCallBack
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="1000" android:fromAlpha="0" android:toAlpha="1" />
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 426 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 859 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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="5dp"/>
|
||||
<solid android:color="#99D8D8D8" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="5dp"/>
|
||||
<solid android:color="#66FFFFFF" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<scale android:scaleWidth="100%">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="5dp"/>
|
||||
<gradient android:startColor="#303CFF" android:centerColor="#216CFF" android:endColor="#25C1F9" android:angle="0"/>
|
||||
</shape>
|
||||
</scale>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -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="#80000000" android:endColor="#00000000" android:angle="90"/>
|
||||
<corners android:radius="8dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:bottomLeftRadius="38dp"
|
||||
android:bottomRightRadius="36dp"
|
||||
android:topLeftRadius="36dp"
|
||||
android:topRightRadius="36dp" />
|
||||
<solid android:color="#80051025" />
|
||||
</shape>
|
||||
@@ -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:endColor="#80000000" android:startColor="#00000000" android:angle="90"/>
|
||||
</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="5dp"/>
|
||||
<solid android:color="#99D8D8D8" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="5dp"/>
|
||||
<solid android:color="#66FFFFFF" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<scale android:scaleWidth="100%">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="5dp"/>
|
||||
<gradient android:startColor="#303CFF" android:centerColor="#216CFF" android:endColor="#25C1F9"/>
|
||||
</shape>
|
||||
</scale>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -2,16 +2,16 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:background="@android:color/holo_orange_dark"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:text="video"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvVideoPlaylist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="731dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="@drawable/bg_taxi_p_video_bg_shape"
|
||||
android:layout_width="@dimen/dp_1300"
|
||||
android:layout_height="@dimen/dp_731">
|
||||
|
||||
<com.mogo.och.bus.passenger.view.ConsultVideoPlayer
|
||||
android:id="@+id/video_item_player"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="@dimen/dp_1300"
|
||||
android:layout_height="@dimen/dp_731" />
|
||||
</FrameLayout>
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<?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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:scaleType="fitXY" />
|
||||
</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="@dimen/dp_180"
|
||||
android:layout_height="@dimen/dp_180"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/layout_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:background="@drawable/bg_taxi_p_video_bg_top"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginStart="40dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="28dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!--局部播放器-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/layout_bottom"
|
||||
android:layout_width="0dp"
|
||||
android:background="@drawable/bg_taxi_p_video_bg"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_height="@dimen/dp_119"
|
||||
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:paddingStart="@dimen/dp_42"
|
||||
android:layout_width="@dimen/dp_98"
|
||||
android:paddingEnd="@dimen/dp_28"
|
||||
android:layout_height="match_parent"
|
||||
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:gravity="bottom"
|
||||
android:text="02:23"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="26dp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dp_16"
|
||||
android:layout_marginEnd="@dimen/dp_31"
|
||||
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="@dimen/dp_7"
|
||||
android:minHeight="@dimen/dp_7"
|
||||
android:splitTrack="false"
|
||||
android:progressDrawable="@drawable/taxi_video_seekbar_style"
|
||||
android:thumb="@drawable/bg_taxi_p_video_index" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/total"
|
||||
android:layout_width="@dimen/dp_106"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toStartOf="@+id/fullscreen"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:gravity="bottom"
|
||||
android:textSize="26dp"
|
||||
android:textColor="@android:color/white"/>
|
||||
<ImageView
|
||||
android:id="@+id/fullscreen"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="@dimen/dp_140"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/notice_video_pause_small" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/v_open_left"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:visibility="gone"
|
||||
android:layout_width="143dp"
|
||||
android:layout_height="308dp"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -20,6 +20,10 @@
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
|
||||
<style name="och_window_anim_alpha">
|
||||
<item name="android:windowEnterAnimation">@anim/alpha_hide_show</item>
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
</resources>
|
||||
@@ -56,6 +56,7 @@ dependencies {
|
||||
|
||||
implementation project(":OCH:mogo-och-common-module")
|
||||
compileOnly project(":libraries:mogo-map")
|
||||
implementation project(':core:mogo-core-res')
|
||||
|
||||
}
|
||||
|
||||
|
||||