feat:初步完成首页星球UI部分

This commit is contained in:
max
2024-02-29 21:10:22 +08:00
parent bffe6916e9
commit 038aded819
34 changed files with 3947 additions and 226 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,29 @@
package com.chwl.app.star
import android.content.res.ColorStateList
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.chwl.app.R
import com.chwl.app.ui.utils.loadAvatar
import com.chwl.core.home.bean.StarUser
import com.example.lib_utils.AppUtils
import com.example.lib_utils.ktx.getColorById
import com.google.android.material.imageview.ShapeableImageView
class StarAdapter : BaseQuickAdapter<StarUser, BaseViewHolder>(R.layout.star_item) {
private val maleColor =
ColorStateList.valueOf(AppUtils.getApp().getColorById(R.color.color_57CDFF))
private val femaleColor =
ColorStateList.valueOf(AppUtils.getApp().getColorById(R.color.color_D667FF))
override fun convert(helper: BaseViewHolder, item: StarUser?) {
val imageView = helper.getView<ShapeableImageView>(R.id.iv_avatar)
if (item?.gender == 1) {
imageView.strokeColor = maleColor
} else {
imageView.strokeColor = femaleColor
}
imageView.loadAvatar(item?.avatar)
helper.setText(R.id.tv_name, "${helper.absoluteAdapterPosition}\n#${item?.nick}")
}
}

View File

@@ -1,9 +1,128 @@
package com.chwl.app.star
import android.util.Log
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.chwl.app.base.BaseViewBindingFragment
import com.chwl.app.databinding.StarFragmentBinding
import com.chwl.app.ui.utils.loadFromAssets
import com.chwl.app.view.layoutmanager.CenterSnapHelper
import com.chwl.app.view.layoutmanager.CircleLayoutManager
import com.chwl.app.view.layoutmanager.ScrollHelper
import com.chwl.core.home.bean.StarUser
import com.example.lib_utils.ktx.singleClick
import com.opensource.svgaplayer.SVGADrawable
import java.io.File
class StarFragment : BaseViewBindingFragment<StarFragmentBinding>() {
private val viewModel: StarViewModel by activityViewModels()
private val adapter = StarAdapter()
private var animFile: File? = null
override fun init() {
initView()
initObserve()
}
private fun initView() {
initRecyclerView()
initAnimView()
binding.ivClose.setOnClickListener {
hideUserPanel()
}
binding.ivGift.setOnClickListener {
}
binding.ivRefresh.singleClick {
viewModel.refresh()
}
binding.ivFollow.singleClick {
}
binding.ivChat.singleClick {
}
}
private fun initObserve() {
viewModel.starUserLiveData.observe(this) {
adapter.setNewData(it)
}
}
private fun initRecyclerView() {
val layoutManager = CircleLayoutManager(context)
layoutManager.infinite = true
layoutManager.angleRotate = false
layoutManager.gravity = CircleLayoutManager.LEFT
layoutManager.minRemoveAngle = -120f
layoutManager.maxRemoveAngle = 120f
CenterSnapHelper().attachToRecyclerView(binding.recyclerView)
binding.recyclerView.post {
val width = binding.recyclerView.width
layoutManager.radius = (width * 0.6).toInt()
layoutManager.angleInterval = 20
layoutManager.distanceToBottom = (width * 0.293f).toInt()
}
binding.recyclerView.layoutManager = layoutManager
adapter.setOnItemClickListener { adapter, view, position ->
toast("$position")
ScrollHelper.smoothScrollToTargetView(binding.recyclerView, view)
(adapter.getItem(position) as? StarUser)?.let {
showUserPanel(it)
}
}
binding.recyclerView.adapter = adapter
}
private fun initAnimView() {
binding.animView.setLoop(Int.MAX_VALUE)
lifecycleScope.launchWhenResumed {
animFile = viewModel.loadMp4File()
animFile?.let {
if (binding.animView.isVisible) {
binding.animView.startPlay(it)
}
}
}
}
private fun showUserPanel(user: StarUser) {
binding.recyclerView.pause()
binding.animView.stopPlay()
binding.animView.isVisible = false
binding.layoutTarget.isVisible = true
binding.layoutTargetInfo.isVisible = true
if ((binding.ivGift.drawable as? SVGADrawable) == null) {
binding.ivGift.loadFromAssets("svga/home_star_gift.svga")
} else {
binding.ivGift.startAnimation()
}
}
private fun hideUserPanel() {
binding.ivGift.pauseAnimation()
binding.recyclerView.start()
binding.animView.isVisible = true
animFile?.let {
binding.animView.startPlay(it)
}
binding.layoutTarget.isVisible = false
binding.layoutTargetInfo.isVisible = false
}
private fun playStarAnim() {
}
private fun pauseStarAnim() {
}
}

View File

@@ -0,0 +1,60 @@
package com.chwl.app.star
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.chwl.app.base.BaseViewModel
import com.chwl.core.home.bean.StarUser
import com.chwl.core.home.model.HomeModel
import com.chwl.core.utils.extension.toast
import com.chwl.library.common.file.FileHelper
import com.example.lib_utils.AppUtils
import java.io.File
class StarViewModel : BaseViewModel() {
private var starUserPage = 1
val starUserLiveData = MutableLiveData<List<StarUser>>(listOf(StarUser()))
fun refresh() {
getStarUser(starUserPage)
}
private fun getStarUser(pageNum: Int) {
safeLaunch(
onError = {
it.message.toast()
},
block = {
val result = HomeModel.getStarUserList(pageNum, 20)
starUserLiveData.value = result ?: emptyList()
}
)
}
suspend fun loadMp4File(): File? {
try {
val fileDir = getMp4FileDir(AppUtils.getApp())
val fileName = "mp4/home_star.mp4"
if (FileHelper.copyFileFromAssets(fileDir, fileName, false)) {
return File(fileDir, fileName)
}
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private fun getMp4FileDir(context: Context): String {
var dir: String
try {
dir = context.getExternalFilesDir(null)?.absolutePath ?: ""
if (dir.isEmpty()) {
dir = context.filesDir.absolutePath
}
} catch (e: Exception) {
dir = context.filesDir.absolutePath
}
return dir
}
}

View File

@@ -0,0 +1,68 @@
package com.chwl.app.view.layoutmanager;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.chwl.app.R;
/**
* An implement of {@link RecyclerView} which support auto play.
*/
public class AutoPlayRecyclerView extends RecyclerView {
private AutoPlaySnapHelper autoPlaySnapHelper;
public AutoPlayRecyclerView(Context context) {
this(context, null);
}
public AutoPlayRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoPlayRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoPlayRecyclerView);
final int distance = typedArray.getInt(R.styleable.AutoPlayRecyclerView_distance, 1);
final int timeInterval = typedArray.getInt(R.styleable.AutoPlayRecyclerView_timeInterval, AutoPlaySnapHelper.TIME_INTERVAL);
final int direction = typedArray.getInt(R.styleable.AutoPlayRecyclerView_direction, AutoPlaySnapHelper.RIGHT);
typedArray.recycle();
autoPlaySnapHelper = new AutoPlaySnapHelper(timeInterval, direction, distance);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = super.dispatchTouchEvent(ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (autoPlaySnapHelper != null) {
autoPlaySnapHelper.pause();
}
break;
case MotionEvent.ACTION_UP:
if (autoPlaySnapHelper != null) {
autoPlaySnapHelper.start();
}
}
return result;
}
public void start() {
autoPlaySnapHelper.start();
}
public void pause() {
autoPlaySnapHelper.pause();
}
@Override
public void setLayoutManager(LayoutManager layout) {
super.setLayoutManager(layout);
autoPlaySnapHelper.attachToRecyclerView(this);
}
}

View File

@@ -0,0 +1,115 @@
package com.chwl.app.view.layoutmanager;
import android.os.Handler;
import android.os.Looper;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
/**
* Used by {@link AutoPlayRecyclerView} to implement auto play effect
*/
class AutoPlaySnapHelper extends CenterSnapHelper {
final static int TIME_INTERVAL = 2000;
final static int LEFT = 1;
final static int RIGHT = 2;
private Handler handler;
private int timeInterval;
private Runnable autoPlayRunnable;
private boolean runnableAdded;
private int direction;
private int distance;
AutoPlaySnapHelper(int timeInterval, int direction, int distance) {
checkTimeInterval(timeInterval);
checkDirection(direction);
handler = new Handler(Looper.getMainLooper());
this.timeInterval = timeInterval;
this.direction = direction;
this.distance = distance;
}
@Override
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
final RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (!(layoutManager instanceof ViewPagerLayoutManager)) return;
setupCallbacks();
mGravityScroller = new Scroller(mRecyclerView.getContext(),
new DecelerateInterpolator());
snapToCenterView((ViewPagerLayoutManager) layoutManager,
((ViewPagerLayoutManager) layoutManager).onPageChangeListener);
((ViewPagerLayoutManager) layoutManager).setInfinite(true);
autoPlayRunnable = new Runnable() {
@Override
public void run() {
ScrollHelper.scrollBy(mRecyclerView,
(ViewPagerLayoutManager) layoutManager, direction == RIGHT ? distance : -distance);
handler.postDelayed(autoPlayRunnable, timeInterval);
}
};
handler.postDelayed(autoPlayRunnable, timeInterval);
runnableAdded = true;
}
}
@Override
void destroyCallbacks() {
super.destroyCallbacks();
if (runnableAdded) {
handler.removeCallbacks(autoPlayRunnable);
runnableAdded = false;
}
}
void pause() {
if (runnableAdded) {
handler.removeCallbacks(autoPlayRunnable);
runnableAdded = false;
}
}
void start() {
if (!runnableAdded) {
handler.postDelayed(autoPlayRunnable, timeInterval);
runnableAdded = true;
}
}
void setTimeInterval(int timeInterval) {
checkTimeInterval(timeInterval);
this.timeInterval = timeInterval;
}
void setDirection(int direction) {
checkDirection(direction);
this.direction = direction;
}
private void checkDirection(int direction) {
if (direction != LEFT && direction != RIGHT)
throw new IllegalArgumentException("direction should be one of left or right");
}
private void checkTimeInterval(int timeInterval) {
if (timeInterval <= 0)
throw new IllegalArgumentException("time interval should greater than 0");
}
}

View File

@@ -0,0 +1,167 @@
package com.chwl.app.view.layoutmanager;
import android.content.Context;
import android.view.View;
/**
* An implementation of {@link ViewPagerLayoutManager}
* which layouts items like carousel
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class CarouselLayoutManager extends ViewPagerLayoutManager {
private int itemSpace;
private float minScale;
private float moveSpeed;
public CarouselLayoutManager(Context context, int itemSpace) {
this(new Builder(context, itemSpace));
}
public CarouselLayoutManager(Context context, int itemSpace, int orientation) {
this(new Builder(context, itemSpace).setOrientation(orientation));
}
public CarouselLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
}
public CarouselLayoutManager(Builder builder) {
this(builder.context, builder.itemSpace, builder.minScale, builder.orientation,
builder.maxVisibleItemCount, builder.moveSpeed, builder.distanceToBottom,
builder.reverseLayout);
}
private CarouselLayoutManager(Context context, int itemSpace, float minScale, int orientation,
int maxVisibleItemCount, float moveSpeed, int distanceToBottom,
boolean reverseLayout) {
super(context, orientation, reverseLayout);
setEnableBringCenterToFront(true);
setDistanceToBottom(distanceToBottom);
setMaxVisibleItemCount(maxVisibleItemCount);
this.itemSpace = itemSpace;
this.minScale = minScale;
this.moveSpeed = moveSpeed;
}
public int getItemSpace() {
return itemSpace;
}
public float getMinScale() {
return minScale;
}
public float getMoveSpeed() {
return moveSpeed;
}
public void setItemSpace(int itemSpace) {
assertNotInLayoutOrScroll(null);
if (this.itemSpace == itemSpace) return;
this.itemSpace = itemSpace;
removeAllViews();
}
public void setMinScale(float minScale) {
assertNotInLayoutOrScroll(null);
if (minScale > 1f) minScale = 1f;
if (this.minScale == minScale) return;
this.minScale = minScale;
requestLayout();
}
public void setMoveSpeed(float moveSpeed) {
assertNotInLayoutOrScroll(null);
if (this.moveSpeed == moveSpeed) return;
this.moveSpeed = moveSpeed;
}
@Override
protected float setInterval() {
return (mDecoratedMeasurement - itemSpace);
}
@Override
protected void setItemViewProperty(View itemView, float targetOffset) {
float scale = calculateScale(targetOffset + mSpaceMain);
itemView.setScaleX(scale);
itemView.setScaleY(scale);
}
@Override
protected float getDistanceRatio() {
if (moveSpeed == 0) return Float.MAX_VALUE;
return 1 / moveSpeed;
}
@Override
protected float setViewElevation(View itemView, float targetOffset) {
return itemView.getScaleX() * 5;
}
private float calculateScale(float x) {
float deltaX = Math.abs(x - (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2f);
return (minScale - 1) * deltaX / (mOrientationHelper.getTotalSpace() / 2f) + 1f;
}
public static class Builder {
private static final float DEFAULT_SPEED = 1f;
private static final float MIN_SCALE = 0.5f;
private Context context;
private int itemSpace;
private int orientation;
private float minScale;
private float moveSpeed;
private int maxVisibleItemCount;
private boolean reverseLayout;
private int distanceToBottom;
public Builder(Context context, int itemSpace) {
this.itemSpace = itemSpace;
this.context = context;
orientation = HORIZONTAL;
minScale = MIN_SCALE;
this.moveSpeed = DEFAULT_SPEED;
reverseLayout = false;
maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
}
public Builder setOrientation(int orientation) {
this.orientation = orientation;
return this;
}
public Builder setMinScale(float minScale) {
this.minScale = minScale;
return this;
}
public Builder setReverseLayout(boolean reverseLayout) {
this.reverseLayout = reverseLayout;
return this;
}
public Builder setMoveSpeed(float moveSpeed) {
this.moveSpeed = moveSpeed;
return this;
}
public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
this.maxVisibleItemCount = maxVisibleItemCount;
return this;
}
public Builder setDistanceToBottom(int distanceToBottom) {
this.distanceToBottom = distanceToBottom;
return this;
}
public CarouselLayoutManager build() {
return new CarouselLayoutManager(this);
}
}
}

View File

@@ -0,0 +1,177 @@
package com.chwl.app.view.layoutmanager;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
/**
* Class intended to support snapping for a {@link RecyclerView}
* which use {@link ViewPagerLayoutManager} as its {@link LayoutManager}.
* <p>
* The implementation will snap the center of the target child view to the center of
* the attached {@link RecyclerView}.
*/
public class CenterSnapHelper extends RecyclerView.OnFlingListener {
RecyclerView mRecyclerView;
Scroller mGravityScroller;
/**
* when the dataSet is extremely large
* {@link #snapToCenterView(ViewPagerLayoutManager, ViewPagerLayoutManager.OnPageChangeListener)}
* may keep calling itself because the accuracy of float
*/
private boolean snapToCenter = false;
// Handles the snap on scroll case.
private final RecyclerView.OnScrollListener mScrollListener =
new RecyclerView.OnScrollListener() {
boolean mScrolled = false;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
final ViewPagerLayoutManager layoutManager =
(ViewPagerLayoutManager) recyclerView.getLayoutManager();
final ViewPagerLayoutManager.OnPageChangeListener onPageChangeListener =
layoutManager.onPageChangeListener;
if (onPageChangeListener != null) {
onPageChangeListener.onPageScrollStateChanged(newState);
}
if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
mScrolled = false;
if (!snapToCenter) {
snapToCenter = true;
snapToCenterView(layoutManager, onPageChangeListener);
} else {
snapToCenter = false;
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dx != 0 || dy != 0) {
mScrolled = true;
}
}
};
@Override
public boolean onFling(int velocityX, int velocityY) {
ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager) mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
if (!layoutManager.getInfinite() &&
(layoutManager.mOffset == layoutManager.getMaxOffset()
|| layoutManager.mOffset == layoutManager.getMinOffset())) {
return false;
}
final int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
mGravityScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL
&& Math.abs(velocityY) > minFlingVelocity) {
final int currentPosition = layoutManager.getCurrentPositionOffset();
final int offsetPosition = (int) (mGravityScroller.getFinalY() /
layoutManager.mInterval / layoutManager.getDistanceRatio());
ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
-currentPosition - offsetPosition : currentPosition + offsetPosition);
return true;
} else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL
&& Math.abs(velocityX) > minFlingVelocity) {
final int currentPosition = layoutManager.getCurrentPositionOffset();
final int offsetPosition = (int) (mGravityScroller.getFinalX() /
layoutManager.mInterval / layoutManager.getDistanceRatio());
ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
-currentPosition - offsetPosition : currentPosition + offsetPosition);
return true;
}
return true;
}
/**
* Please attach after {{@link LayoutManager} is setting}
* Attaches the {@link CenterSnapHelper} to the provided RecyclerView, by calling
* {@link RecyclerView#setOnFlingListener(RecyclerView.OnFlingListener)}.
* You can call this method with {@code null} to detach it from the current RecyclerView.
*
* @param recyclerView The RecyclerView instance to which you want to add this helper or
* {@code null} if you want to remove CenterSnapHelper from the current
* RecyclerView.
* @throws IllegalArgumentException if there is already a {@link RecyclerView.OnFlingListener}
* attached to the provided {@link RecyclerView}.
*/
public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
throws IllegalStateException {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
final RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (!(layoutManager instanceof ViewPagerLayoutManager)) return;
setupCallbacks();
mGravityScroller = new Scroller(mRecyclerView.getContext(),
new DecelerateInterpolator());
snapToCenterView((ViewPagerLayoutManager) layoutManager,
((ViewPagerLayoutManager) layoutManager).onPageChangeListener);
}
}
void snapToCenterView(ViewPagerLayoutManager layoutManager,
ViewPagerLayoutManager.OnPageChangeListener listener) {
final int delta = layoutManager.getOffsetToCenter();
if (delta != 0) {
if (layoutManager.getOrientation()
== ViewPagerLayoutManager.VERTICAL)
mRecyclerView.smoothScrollBy(0, delta);
else
mRecyclerView.smoothScrollBy(delta, 0);
} else {
// set it false to make smoothScrollToPosition keep trigger the listener
snapToCenter = false;
}
if (listener != null)
listener.onPageSelected(layoutManager.getCurrentPosition());
}
/**
* Called when an instance of a {@link RecyclerView} is attached.
*/
void setupCallbacks() throws IllegalStateException {
if (mRecyclerView.getOnFlingListener() != null) {
throw new IllegalStateException("An instance of OnFlingListener already set.");
}
mRecyclerView.addOnScrollListener(mScrollListener);
mRecyclerView.setOnFlingListener(this);
}
/**
* Called when the instance of a {@link RecyclerView} is detached.
*/
void destroyCallbacks() {
mRecyclerView.removeOnScrollListener(mScrollListener);
mRecyclerView.setOnFlingListener(null);
}
}

View File

@@ -0,0 +1,376 @@
package com.chwl.app.view.layoutmanager;
import android.content.Context;
import android.view.View;
/**
* An implementation of {@link ViewPagerLayoutManager}
* which layouts item in a circle
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class CircleLayoutManager extends ViewPagerLayoutManager {
public static final int LEFT = 10;
public static final int RIGHT = 11;
public static final int TOP = 12;
public static final int BOTTOM = 13;
public static final int LEFT_ON_TOP = 4;
public static final int RIGHT_ON_TOP = 5;
public static final int CENTER_ON_TOP = 6;
private int radius;
private int angleInterval;
private float moveSpeed;
private float maxRemoveAngle;
private float minRemoveAngle;
private int gravity;
private boolean flipRotate;
private int zAlignment;
private boolean angleRotate;
public CircleLayoutManager(Context context) {
this(new Builder(context));
}
public CircleLayoutManager(Context context, boolean reverseLayout) {
this(new Builder(context).setReverseLayout(reverseLayout));
}
public CircleLayoutManager(Context context, int gravity, boolean reverseLayout) {
this(new Builder(context).setGravity(gravity).setReverseLayout(reverseLayout));
}
public CircleLayoutManager(Builder builder) {
this(builder.context, builder.radius, builder.angleInterval, builder.moveSpeed, builder.maxRemoveAngle,
builder.minRemoveAngle, builder.gravity, builder.zAlignment, builder.flipRotate, builder.angleRotate,
builder.maxVisibleItemCount, builder.distanceToBottom, builder.reverseLayout);
}
private CircleLayoutManager(Context context, int radius, int angleInterval, float moveSpeed,
float max, float min, int gravity, int zAlignment, boolean flipRotate, boolean angleRotate,
int maxVisibleItemCount, int distanceToBottom, boolean reverseLayout) {
super(context, (gravity == LEFT || gravity == RIGHT) ? VERTICAL : HORIZONTAL, reverseLayout);
setEnableBringCenterToFront(true);
setMaxVisibleItemCount(maxVisibleItemCount);
setDistanceToBottom(distanceToBottom);
this.radius = radius;
this.angleInterval = angleInterval;
this.moveSpeed = moveSpeed;
this.maxRemoveAngle = max;
this.minRemoveAngle = min;
this.gravity = gravity;
this.flipRotate = flipRotate;
this.angleRotate = angleRotate;
this.zAlignment = zAlignment;
}
public int getRadius() {
return radius;
}
public int getAngleInterval() {
return angleInterval;
}
public float getMoveSpeed() {
return moveSpeed;
}
public float getMaxRemoveAngle() {
return maxRemoveAngle;
}
public float getMinRemoveAngle() {
return minRemoveAngle;
}
public int getGravity() {
return gravity;
}
public boolean getFlipRotate() {
return flipRotate;
}
public boolean getAngleRotate() {
return angleRotate;
}
public int getZAlignment() {
return zAlignment;
}
public void setRadius(int radius) {
assertNotInLayoutOrScroll(null);
if (this.radius == radius) return;
this.radius = radius;
removeAllViews();
}
public void setAngleInterval(int angleInterval) {
assertNotInLayoutOrScroll(null);
if (this.angleInterval == angleInterval) return;
this.angleInterval = angleInterval;
removeAllViews();
}
public void setMoveSpeed(float moveSpeed) {
assertNotInLayoutOrScroll(null);
if (this.moveSpeed == moveSpeed) return;
this.moveSpeed = moveSpeed;
}
public void setMaxRemoveAngle(float maxRemoveAngle) {
assertNotInLayoutOrScroll(null);
if (this.maxRemoveAngle == maxRemoveAngle) return;
this.maxRemoveAngle = maxRemoveAngle;
requestLayout();
}
public void setMinRemoveAngle(float minRemoveAngle) {
assertNotInLayoutOrScroll(null);
if (this.minRemoveAngle == minRemoveAngle) return;
this.minRemoveAngle = minRemoveAngle;
requestLayout();
}
public void setGravity(int gravity) {
assertNotInLayoutOrScroll(null);
assertGravity(gravity);
if (this.gravity == gravity) return;
this.gravity = gravity;
if (gravity == LEFT || gravity == RIGHT) {
setOrientation(VERTICAL);
} else {
setOrientation(HORIZONTAL);
}
requestLayout();
}
public void setFlipRotate(boolean flipRotate) {
assertNotInLayoutOrScroll(null);
if (this.flipRotate == flipRotate) return;
this.flipRotate = flipRotate;
requestLayout();
}
public void setAngleRotate(boolean angleRotate) {
assertNotInLayoutOrScroll(null);
if (this.angleRotate == angleRotate) return;
this.angleRotate = angleRotate;
requestLayout();
}
public void setZAlignment(int zAlignment) {
assertNotInLayoutOrScroll(null);
assertZAlignmentState(zAlignment);
if (this.zAlignment == zAlignment) return;
this.zAlignment = zAlignment;
requestLayout();
}
@Override
protected float setInterval() {
return angleInterval;
}
@Override
protected void setUp() {
radius = radius == Builder.INVALID_VALUE ? mDecoratedMeasurementInOther : radius;
}
@Override
protected float maxRemoveOffset() {
return maxRemoveAngle;
}
@Override
protected float minRemoveOffset() {
return minRemoveAngle;
}
@Override
protected int calItemLeft(View itemView, float targetOffset) {
switch (gravity) {
case LEFT:
return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius);
case RIGHT:
return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset)));
case TOP:
case BOTTOM:
default:
return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset)));
}
}
@Override
protected int calItemTop(View itemView, float targetOffset) {
switch (gravity) {
case LEFT:
case RIGHT:
return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset)));
case TOP:
return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius);
case BOTTOM:
default:
return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset)));
}
}
@Override
protected void setItemViewProperty(View itemView, float targetOffset) {
switch (gravity) {
case RIGHT:
case TOP:
if (flipRotate) {
itemView.setRotation(targetOffset);
} else if (angleRotate) {
itemView.setRotation(360 - targetOffset);
}
break;
case LEFT:
case BOTTOM:
default:
if (flipRotate) {
itemView.setRotation(360 - targetOffset);
} else if (angleRotate) {
itemView.setRotation(targetOffset);
}
break;
}
}
@Override
protected float setViewElevation(View itemView, float targetOffset) {
if (zAlignment == LEFT_ON_TOP)
return (540 - targetOffset) / 72;
else if (zAlignment == RIGHT_ON_TOP)
return (targetOffset - 540) / 72;
else
return (360 - Math.abs(targetOffset)) / 72;
}
@Override
protected float getDistanceRatio() {
if (moveSpeed == 0) return Float.MAX_VALUE;
return 1 / moveSpeed;
}
private static void assertGravity(int gravity) {
if (gravity != LEFT && gravity != RIGHT && gravity != TOP && gravity != BOTTOM) {
throw new IllegalArgumentException("gravity must be one of LEFT RIGHT TOP and BOTTOM");
}
}
private static void assertZAlignmentState(int zAlignment) {
if (zAlignment != LEFT_ON_TOP && zAlignment != RIGHT_ON_TOP && zAlignment != CENTER_ON_TOP) {
throw new IllegalArgumentException("zAlignment must be one of LEFT_ON_TOP RIGHT_ON_TOP and CENTER_ON_TOP");
}
}
public static class Builder {
private static int INTERVAL_ANGLE = 30;// The default mInterval angle between each items
private static float DISTANCE_RATIO = 10f; // Finger swipe distance divide item rotate angle
private static int INVALID_VALUE = Integer.MIN_VALUE;
private static int MAX_REMOVE_ANGLE = 90;
private static int MIN_REMOVE_ANGLE = -90;
private int radius;
private int angleInterval;
private float moveSpeed;
private float maxRemoveAngle;
private float minRemoveAngle;
private boolean reverseLayout;
private Context context;
private int gravity;
private boolean flipRotate;
private boolean angleRotate;
private int zAlignment;
private int maxVisibleItemCount;
private int distanceToBottom;
public Builder(Context context) {
this.context = context;
radius = INVALID_VALUE;
angleInterval = INTERVAL_ANGLE;
moveSpeed = 1 / DISTANCE_RATIO;
maxRemoveAngle = MAX_REMOVE_ANGLE;
minRemoveAngle = MIN_REMOVE_ANGLE;
reverseLayout = false;
flipRotate = false;
angleRotate = true;
gravity = BOTTOM;
zAlignment = LEFT_ON_TOP;
maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
}
public Builder setRadius(int radius) {
this.radius = radius;
return this;
}
public Builder setAngleInterval(int angleInterval) {
this.angleInterval = angleInterval;
return this;
}
public Builder setMoveSpeed(int moveSpeed) {
this.moveSpeed = moveSpeed;
return this;
}
public Builder setMaxRemoveAngle(float maxRemoveAngle) {
this.maxRemoveAngle = maxRemoveAngle;
return this;
}
public Builder setMinRemoveAngle(float minRemoveAngle) {
this.minRemoveAngle = minRemoveAngle;
return this;
}
public Builder setReverseLayout(boolean reverseLayout) {
this.reverseLayout = reverseLayout;
return this;
}
public Builder setGravity(int gravity) {
assertGravity(gravity);
this.gravity = gravity;
return this;
}
public Builder setFlipRotate(boolean flipRotate) {
this.flipRotate = flipRotate;
return this;
}
public Builder setAngleRotate(boolean angleRotate) {
this.angleRotate = angleRotate;
return this;
}
public Builder setZAlignment(int zAlignment) {
assertZAlignmentState(zAlignment);
this.zAlignment = zAlignment;
return this;
}
public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
this.maxVisibleItemCount = maxVisibleItemCount;
return this;
}
public Builder setDistanceToBottom(int distanceToBottom) {
this.distanceToBottom = distanceToBottom;
return this;
}
public CircleLayoutManager build() {
return new CircleLayoutManager(this);
}
}
}

View File

@@ -0,0 +1,393 @@
package com.chwl.app.view.layoutmanager;
import android.content.Context;
import android.view.View;
/**
* An implementation of {@link ViewPagerLayoutManager}
* which layouts item in a circle and will change the child's centerScale while scrolling
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class CircleScaleLayoutManager extends ViewPagerLayoutManager {
public static final int LEFT = 10;
public static final int RIGHT = 11;
public static final int TOP = 12;
public static final int BOTTOM = 13;
public static final int LEFT_ON_TOP = 4;
public static final int RIGHT_ON_TOP = 5;
public static final int CENTER_ON_TOP = 6;
private int radius;
private int angleInterval;
private float moveSpeed;
private float centerScale;
private float maxRemoveAngle;
private float minRemoveAngle;
private int gravity;
private boolean flipRotate;
private int zAlignment;
public CircleScaleLayoutManager(Context context) {
this(new Builder(context));
}
public CircleScaleLayoutManager(Context context, int gravity, boolean reverseLayout) {
this(new Builder(context).setGravity(gravity).setReverseLayout(reverseLayout));
}
public CircleScaleLayoutManager(Context context, boolean reverseLayout) {
this(new Builder(context).setReverseLayout(reverseLayout));
}
public CircleScaleLayoutManager(Builder builder) {
this(builder.context, builder.radius, builder.angleInterval, builder.centerScale, builder.moveSpeed,
builder.maxRemoveAngle, builder.minRemoveAngle, builder.gravity, builder.zAlignment,
builder.flipRotate, builder.maxVisibleItemCount, builder.distanceToBottom, builder.reverseLayout);
}
private CircleScaleLayoutManager(Context context, int radius, int angleInterval, float centerScale,
float moveSpeed, float max, float min, int gravity, int zAlignment,
boolean flipRotate, int maxVisibleItemCount, int distanceToBottom, boolean reverseLayout) {
super(context, HORIZONTAL, reverseLayout);
setEnableBringCenterToFront(true);
setMaxVisibleItemCount(maxVisibleItemCount);
setDistanceToBottom(distanceToBottom);
this.radius = radius;
this.angleInterval = angleInterval;
this.centerScale = centerScale;
this.moveSpeed = moveSpeed;
this.maxRemoveAngle = max;
this.minRemoveAngle = min;
this.gravity = gravity;
this.flipRotate = flipRotate;
this.zAlignment = zAlignment;
}
public int getRadius() {
return radius;
}
public int getAngleInterval() {
return angleInterval;
}
public float getCenterScale() {
return centerScale;
}
public float getMoveSpeed() {
return moveSpeed;
}
public float getMaxRemoveAngle() {
return maxRemoveAngle;
}
public float getMinRemoveAngle() {
return minRemoveAngle;
}
public int getGravity() {
return gravity;
}
public boolean getFlipRotate() {
return flipRotate;
}
public int getZAlignment() {
return zAlignment;
}
public void setRadius(int radius) {
assertNotInLayoutOrScroll(null);
if (this.radius == radius) return;
this.radius = radius;
removeAllViews();
}
public void setAngleInterval(int angleInterval) {
assertNotInLayoutOrScroll(null);
if (this.angleInterval == angleInterval) return;
this.angleInterval = angleInterval;
removeAllViews();
}
public void setCenterScale(float centerScale) {
assertNotInLayoutOrScroll(null);
if (this.centerScale == centerScale) return;
this.centerScale = centerScale;
requestLayout();
}
public void setMoveSpeed(float moveSpeed) {
assertNotInLayoutOrScroll(null);
if (this.moveSpeed == moveSpeed) return;
this.moveSpeed = moveSpeed;
}
public void setMaxRemoveAngle(float maxRemoveAngle) {
assertNotInLayoutOrScroll(null);
if (this.maxRemoveAngle == maxRemoveAngle) return;
this.maxRemoveAngle = maxRemoveAngle;
requestLayout();
}
public void setMinRemoveAngle(float minRemoveAngle) {
assertNotInLayoutOrScroll(null);
if (this.minRemoveAngle == minRemoveAngle) return;
this.minRemoveAngle = minRemoveAngle;
requestLayout();
}
public void setGravity(int gravity) {
assertNotInLayoutOrScroll(null);
assertGravity(gravity);
if (this.gravity == gravity) return;
this.gravity = gravity;
if (gravity == LEFT || gravity == RIGHT) {
setOrientation(VERTICAL);
} else {
setOrientation(HORIZONTAL);
}
requestLayout();
}
public void setFlipRotate(boolean flipRotate) {
assertNotInLayoutOrScroll(null);
if (this.flipRotate == flipRotate) return;
this.flipRotate = flipRotate;
requestLayout();
}
public void setZAlignment(int zAlignment) {
assertNotInLayoutOrScroll(null);
assertZAlignmentState(zAlignment);
if (this.zAlignment == zAlignment) return;
this.zAlignment = zAlignment;
requestLayout();
}
@Override
protected float setInterval() {
return angleInterval;
}
@Override
protected void setUp() {
radius = radius == Builder.INVALID_VALUE ? mDecoratedMeasurementInOther : radius;
}
@Override
protected float maxRemoveOffset() {
return maxRemoveAngle;
}
@Override
protected float minRemoveOffset() {
return minRemoveAngle;
}
@Override
protected int calItemLeft(View itemView, float targetOffset) {
switch (gravity) {
case LEFT:
return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius);
case RIGHT:
return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset)));
case TOP:
case BOTTOM:
default:
return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset)));
}
}
@Override
protected int calItemTop(View itemView, float targetOffset) {
switch (gravity) {
case LEFT:
case RIGHT:
return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset)));
case TOP:
return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius);
case BOTTOM:
default:
return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset)));
}
}
@Override
protected void setItemViewProperty(View itemView, float targetOffset) {
float scale = 1f;
switch (gravity) {
case RIGHT:
case TOP:
if (flipRotate) {
itemView.setRotation(targetOffset);
if (targetOffset < angleInterval && targetOffset > -angleInterval) {
float diff = Math.abs(Math.abs(itemView.getRotation() - angleInterval) - angleInterval);
scale = (centerScale - 1f) / -angleInterval * diff + centerScale;
}
} else {
itemView.setRotation(360 - targetOffset);
if (targetOffset < angleInterval && targetOffset > -angleInterval) {
float diff = Math.abs(Math.abs(360 - itemView.getRotation() - angleInterval) - angleInterval);
scale = (centerScale - 1f) / -angleInterval * diff + centerScale;
}
}
break;
case LEFT:
case BOTTOM:
default:
if (flipRotate) {
itemView.setRotation(360 - targetOffset);
if (targetOffset < angleInterval && targetOffset > -angleInterval) {
float diff = Math.abs(Math.abs(360 - itemView.getRotation() - angleInterval) - angleInterval);
scale = (centerScale - 1f) / -angleInterval * diff + centerScale;
}
} else {
itemView.setRotation(targetOffset);
if (targetOffset < angleInterval && targetOffset > -angleInterval) {
float diff = Math.abs(Math.abs(itemView.getRotation() - angleInterval) - angleInterval);
scale = (centerScale - 1f) / -angleInterval * diff + centerScale;
}
}
break;
}
itemView.setScaleX(scale);
itemView.setScaleY(scale);
}
@Override
protected float setViewElevation(View itemView, float targetOffset) {
if (zAlignment == LEFT_ON_TOP)
return (540 - targetOffset) / 72;
else if (zAlignment == RIGHT_ON_TOP)
return (targetOffset - 540) / 72;
else
return (360 - Math.abs(targetOffset)) / 72;
}
@Override
protected float getDistanceRatio() {
if (moveSpeed == 0) return Float.MAX_VALUE;
return 1 / moveSpeed;
}
private static void assertGravity(int gravity) {
if (gravity != LEFT && gravity != RIGHT && gravity != TOP && gravity != BOTTOM) {
throw new IllegalArgumentException("gravity must be one of LEFT RIGHT TOP and BOTTOM");
}
}
private static void assertZAlignmentState(int zAlignment) {
if (zAlignment != LEFT_ON_TOP && zAlignment != RIGHT_ON_TOP && zAlignment != CENTER_ON_TOP) {
throw new IllegalArgumentException("zAlignment must be one of LEFT_ON_TOP RIGHT_ON_TOP and CENTER_ON_TOP");
}
}
public static class Builder {
private static int INTERVAL_ANGLE = 30;// The default mInterval angle between each items
private static float DISTANCE_RATIO = 10f; // Finger swipe distance divide item rotate angle
private static final float SCALE_RATE = 1.2f;
private static int INVALID_VALUE = Integer.MIN_VALUE;
private int radius;
private int angleInterval;
private float centerScale;
private float moveSpeed;
private float maxRemoveAngle;
private float minRemoveAngle;
private boolean reverseLayout;
private Context context;
private int gravity;
private boolean flipRotate;
private int zAlignment;
private int maxVisibleItemCount;
private int distanceToBottom;
public Builder(Context context) {
this.context = context;
radius = INVALID_VALUE;
angleInterval = INTERVAL_ANGLE;
centerScale = SCALE_RATE;
moveSpeed = 1 / DISTANCE_RATIO;
maxRemoveAngle = 90;
minRemoveAngle = -90;
reverseLayout = false;
flipRotate = false;
gravity = BOTTOM;
zAlignment = CENTER_ON_TOP;
distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
}
public Builder setRadius(int radius) {
this.radius = radius;
return this;
}
public Builder setAngleInterval(int angleInterval) {
this.angleInterval = angleInterval;
return this;
}
public Builder setCenterScale(float centerScale) {
this.centerScale = centerScale;
return this;
}
public Builder setMoveSpeed(int moveSpeed) {
this.moveSpeed = moveSpeed;
return this;
}
public Builder setMaxRemoveAngle(float maxRemoveAngle) {
this.maxRemoveAngle = maxRemoveAngle;
return this;
}
public Builder setMinRemoveAngle(float minRemoveAngle) {
this.minRemoveAngle = minRemoveAngle;
return this;
}
public Builder setReverseLayout(boolean reverseLayout) {
this.reverseLayout = reverseLayout;
return this;
}
public Builder setGravity(int gravity) {
assertGravity(gravity);
this.gravity = gravity;
return this;
}
public Builder setFlipRotate(boolean flipRotate) {
this.flipRotate = flipRotate;
return this;
}
public Builder setZAlignment(int zAlignment) {
assertZAlignmentState(zAlignment);
this.zAlignment = zAlignment;
return this;
}
public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
this.maxVisibleItemCount = maxVisibleItemCount;
return this;
}
public Builder setDistanceToBottom(int distanceToBottom) {
this.distanceToBottom = distanceToBottom;
return this;
}
public CircleScaleLayoutManager build() {
return new CircleScaleLayoutManager(this);
}
}
}

View File

@@ -0,0 +1,286 @@
package com.chwl.app.view.layoutmanager;
import android.content.Context;
import android.view.View;
/**
* An implementation of {@link ViewPagerLayoutManager}
* which will change rotate x or rotate y
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class GalleryLayoutManager extends ViewPagerLayoutManager {
private final float MAX_ELEVATION = 5F;
private int itemSpace;
private float moveSpeed;
private float maxAlpha;
private float minAlpha;
private float angle;
private boolean flipRotate;
private boolean rotateFromEdge;
public GalleryLayoutManager(Context context, int itemSpace) {
this(new Builder(context, itemSpace));
}
public GalleryLayoutManager(Context context, int itemSpace, int orientation) {
this(new Builder(context, itemSpace).setOrientation(orientation));
}
public GalleryLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
}
public GalleryLayoutManager(Builder builder) {
this(builder.context, builder.itemSpace, builder.angle, builder.maxAlpha, builder.minAlpha,
builder.orientation, builder.moveSpeed, builder.flipRotate, builder.rotateFromEdge,
builder.maxVisibleItemCount, builder.distanceToBottom, builder.reverseLayout);
}
private GalleryLayoutManager(Context context, int itemSpace, float angle, float maxAlpha, float minAlpha,
int orientation, float moveSpeed, boolean flipRotate, boolean rotateFromEdge,
int maxVisibleItemCount, int distanceToBottom, boolean reverseLayout) {
super(context, orientation, reverseLayout);
setDistanceToBottom(distanceToBottom);
setMaxVisibleItemCount(maxVisibleItemCount);
this.itemSpace = itemSpace;
this.moveSpeed = moveSpeed;
this.angle = angle;
this.maxAlpha = maxAlpha;
this.minAlpha = minAlpha;
this.flipRotate = flipRotate;
this.rotateFromEdge = rotateFromEdge;
}
public int getItemSpace() {
return itemSpace;
}
public float getMaxAlpha() {
return maxAlpha;
}
public float getMinAlpha() {
return minAlpha;
}
public float getAngle() {
return angle;
}
public float getMoveSpeed() {
return moveSpeed;
}
public boolean getFlipRotate() {
return flipRotate;
}
public boolean getRotateFromEdge() {
return rotateFromEdge;
}
public void setItemSpace(int itemSpace) {
assertNotInLayoutOrScroll(null);
if (this.itemSpace == itemSpace) return;
this.itemSpace = itemSpace;
removeAllViews();
}
public void setMoveSpeed(float moveSpeed) {
assertNotInLayoutOrScroll(null);
if (this.moveSpeed == moveSpeed) return;
this.moveSpeed = moveSpeed;
}
public void setMaxAlpha(float maxAlpha) {
assertNotInLayoutOrScroll(null);
if (maxAlpha > 1) maxAlpha = 1;
if (this.maxAlpha == maxAlpha) return;
this.maxAlpha = maxAlpha;
requestLayout();
}
public void setMinAlpha(float minAlpha) {
assertNotInLayoutOrScroll(null);
if (minAlpha < 0) minAlpha = 0;
if (this.minAlpha == minAlpha) return;
this.minAlpha = minAlpha;
requestLayout();
}
public void setAngle(float angle) {
assertNotInLayoutOrScroll(null);
if (this.angle == angle) return;
this.angle = angle;
requestLayout();
}
public void setFlipRotate(boolean flipRotate) {
assertNotInLayoutOrScroll(null);
if (this.flipRotate == flipRotate) return;
this.flipRotate = flipRotate;
requestLayout();
}
public void setRotateFromEdge(boolean rotateFromEdge) {
assertNotInLayoutOrScroll(null);
if (this.rotateFromEdge == rotateFromEdge) return;
this.rotateFromEdge = rotateFromEdge;
removeAllViews();
}
@Override
protected float setInterval() {
return mDecoratedMeasurement + itemSpace;
}
@Override
protected void setItemViewProperty(View itemView, float targetOffset) {
final float rotation = calRotation(targetOffset);
if (getOrientation() == HORIZONTAL) {
if (rotateFromEdge) {
itemView.setPivotX(rotation > 0 ? 0 : mDecoratedMeasurement);
itemView.setPivotY(mDecoratedMeasurementInOther * 0.5f);
}
if (flipRotate) {
itemView.setRotationX(rotation);
} else {
itemView.setRotationY(rotation);
}
} else {
if (rotateFromEdge) {
itemView.setPivotY(rotation > 0 ? 0 : mDecoratedMeasurement);
itemView.setPivotX(mDecoratedMeasurementInOther * 0.5f);
}
if (flipRotate) {
itemView.setRotationY(-rotation);
} else {
itemView.setRotationX(-rotation);
}
}
final float alpha = calAlpha(targetOffset);
itemView.setAlpha(alpha);
}
@Override
protected float setViewElevation(View itemView, float targetOffset) {
final float ele = Math.max(Math.abs(itemView.getRotationX()), Math.abs(itemView.getRotationY())) * MAX_ELEVATION / 360;
return MAX_ELEVATION - ele;
}
@Override
protected float getDistanceRatio() {
if (moveSpeed == 0) return Float.MAX_VALUE;
return 1 / moveSpeed;
}
private float calRotation(float targetOffset) {
return -angle / mInterval * targetOffset;
}
private float calAlpha(float targetOffset) {
final float offset = Math.abs(targetOffset);
float alpha = (minAlpha - maxAlpha) / mInterval * offset + maxAlpha;
if (offset >= mInterval) alpha = minAlpha;
return alpha;
}
public static class Builder {
private static float INTERVAL_ANGLE = 30f;
private static final float DEFAULT_SPEED = 1f;
private static float MIN_ALPHA = 0.5f;
private static float MAX_ALPHA = 1f;
private int itemSpace;
private float moveSpeed;
private int orientation;
private float maxAlpha;
private float minAlpha;
private float angle;
private boolean flipRotate;
private boolean reverseLayout;
private Context context;
private int maxVisibleItemCount;
private int distanceToBottom;
private boolean rotateFromEdge;
public Builder(Context context, int itemSpace) {
this.itemSpace = itemSpace;
this.context = context;
orientation = HORIZONTAL;
angle = INTERVAL_ANGLE;
maxAlpha = MAX_ALPHA;
minAlpha = MIN_ALPHA;
this.moveSpeed = DEFAULT_SPEED;
reverseLayout = false;
flipRotate = false;
rotateFromEdge = false;
distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
}
public Builder setItemSpace(int itemSpace) {
this.itemSpace = itemSpace;
return this;
}
public Builder setMoveSpeed(float moveSpeed) {
this.moveSpeed = moveSpeed;
return this;
}
public Builder setOrientation(int orientation) {
this.orientation = orientation;
return this;
}
public Builder setMaxAlpha(float maxAlpha) {
if (maxAlpha > 1) maxAlpha = 1;
this.maxAlpha = maxAlpha;
return this;
}
public Builder setMinAlpha(float minAlpha) {
if (minAlpha < 0) minAlpha = 0;
this.minAlpha = minAlpha;
return this;
}
public Builder setAngle(float angle) {
this.angle = angle;
return this;
}
public Builder setFlipRotate(boolean flipRotate) {
this.flipRotate = flipRotate;
return this;
}
public Builder setReverseLayout(boolean reverseLayout) {
this.reverseLayout = reverseLayout;
return this;
}
public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
this.maxVisibleItemCount = maxVisibleItemCount;
return this;
}
public Builder setDistanceToBottom(int distanceToBottom) {
this.distanceToBottom = distanceToBottom;
return this;
}
public Builder setRotateFromEdge(boolean rotateFromEdge) {
this.rotateFromEdge = rotateFromEdge;
return this;
}
public GalleryLayoutManager build() {
return new GalleryLayoutManager(this);
}
}
}

View File

@@ -0,0 +1,423 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chwl.app.view.layoutmanager;
import android.graphics.Rect;
import android.view.View;
import android.widget.LinearLayout;
import androidx.recyclerview.widget.RecyclerView;
/**
* Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
* <p>
* It is developed to easily support vertical and horizontal orientations in a LayoutManager but
* can also be used to abstract calls around view bounds and child measurements with margins and
* decorations.
*
* @see #createHorizontalHelper(RecyclerView.LayoutManager)
* @see #createVerticalHelper(RecyclerView.LayoutManager)
*/
@SuppressWarnings({"WeakerAccess","unused"})
public abstract class OrientationHelper {
private static final int INVALID_SIZE = Integer.MIN_VALUE;
protected final RecyclerView.LayoutManager mLayoutManager;
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
private int mLastTotalSpace = INVALID_SIZE;
final Rect mTmpRect = new Rect();
private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
mLayoutManager = layoutManager;
}
/**
* Call this method after onLayout method is complete if state is NOT pre-layout.
* This method records information like layout bounds that might be useful in the next layout
* calculations.
*/
public void onLayoutComplete() {
mLastTotalSpace = getTotalSpace();
}
/**
* Returns the layout space change between the previous layout pass and current layout pass.
* <p>
* Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
* {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
* RecyclerView.State)} method.
*
* @return The difference between the current total space and previous layout's total space.
* @see #onLayoutComplete()
*/
public int getTotalSpaceChange() {
return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
}
/**
* Returns the start of the view including its decoration and margin.
* <p>
* For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
* decoration and 3px left margin, returned value will be 15px.
*
* @param view The view element to check
* @return The first pixel of the element
* @see #getDecoratedEnd(View)
*/
public abstract int getDecoratedStart(View view);
/**
* Returns the end of the view including its decoration and margin.
* <p>
* For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
* decoration and 3px right margin, returned value will be 205.
*
* @param view The view element to check
* @return The last pixel of the element
* @see #getDecoratedStart(View)
*/
public abstract int getDecoratedEnd(View view);
/**
* Returns the end of the View after its matrix transformations are applied to its layout
* position.
* <p>
* This method is useful when trying to detect the visible edge of a View.
* <p>
* It includes the decorations but does not include the margins.
*
* @param view The view whose transformed end will be returned
* @return The end of the View after its decor insets and transformation matrix is applied to
* its position
* @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
*/
public abstract int getTransformedEndWithDecoration(View view);
/**
* Returns the start of the View after its matrix transformations are applied to its layout
* position.
* <p>
* This method is useful when trying to detect the visible edge of a View.
* <p>
* It includes the decorations but does not include the margins.
*
* @param view The view whose transformed start will be returned
* @return The start of the View after its decor insets and transformation matrix is applied to
* its position
* @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
*/
public abstract int getTransformedStartWithDecoration(View view);
/**
* Returns the space occupied by this View in the current orientation including decorations and
* margins.
*
* @param view The view element to check
* @return Total space occupied by this view
* @see #getDecoratedMeasurementInOther(View)
*/
public abstract int getDecoratedMeasurement(View view);
/**
* Returns the space occupied by this View in the perpendicular orientation including
* decorations and margins.
*
* @param view The view element to check
* @return Total space occupied by this view in the perpendicular orientation to current one
* @see #getDecoratedMeasurement(View)
*/
public abstract int getDecoratedMeasurementInOther(View view);
/**
* Returns the start position of the layout after the start padding is added.
*
* @return The very first pixel we can draw.
*/
public abstract int getStartAfterPadding();
/**
* Returns the end position of the layout after the end padding is removed.
*
* @return The end boundary for this layout.
*/
public abstract int getEndAfterPadding();
/**
* Returns the end position of the layout without taking padding into account.
*
* @return The end boundary for this layout without considering padding.
*/
public abstract int getEnd();
/**
* Returns the total space to layout. This number is the difference between
* {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
*
* @return Total space to layout children
*/
public abstract int getTotalSpace();
/**
* Returns the total space in other direction to layout. This number is the difference between
* {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
*
* @return Total space to layout children
*/
public abstract int getTotalSpaceInOther();
/**
* Returns the padding at the end of the layout. For horizontal helper, this is the right
* padding and for vertical helper, this is the bottom padding. This method does not check
* whether the layout is RTL or not.
*
* @return The padding at the end of the layout.
*/
public abstract int getEndPadding();
/**
* Returns the MeasureSpec mode for the current orientation from the LayoutManager.
*
* @return The current measure spec mode.
* @see View.MeasureSpec
* @see RecyclerView.LayoutManager#getWidthMode()
* @see RecyclerView.LayoutManager#getHeightMode()
*/
public abstract int getMode();
/**
* Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager.
*
* @return The current measure spec mode.
* @see View.MeasureSpec
* @see RecyclerView.LayoutManager#getWidthMode()
* @see RecyclerView.LayoutManager#getHeightMode()
*/
public abstract int getModeInOther();
/**
* Creates an OrientationHelper for the given LayoutManager and orientation.
*
* @param layoutManager LayoutManager to attach to
* @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
* @return A new OrientationHelper
*/
public static OrientationHelper createOrientationHelper(
RecyclerView.LayoutManager layoutManager, int orientation) {
switch (orientation) {
case HORIZONTAL:
return createHorizontalHelper(layoutManager);
case VERTICAL:
return createVerticalHelper(layoutManager);
}
throw new IllegalArgumentException("invalid orientation");
}
/**
* Creates a horizontal OrientationHelper for the given LayoutManager.
*
* @param layoutManager The LayoutManager to attach to.
* @return A new OrientationHelper
*/
public static OrientationHelper createHorizontalHelper(
RecyclerView.LayoutManager layoutManager) {
return new OrientationHelper(layoutManager) {
@Override
public int getEndAfterPadding() {
return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
}
@Override
public int getEnd() {
return mLayoutManager.getWidth();
}
@Override
public int getStartAfterPadding() {
return mLayoutManager.getPaddingLeft();
}
@Override
public int getDecoratedMeasurement(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ params.rightMargin;
}
@Override
public int getDecoratedMeasurementInOther(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ params.bottomMargin;
}
@Override
public int getDecoratedEnd(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
}
@Override
public int getDecoratedStart(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
}
@Override
public int getTransformedEndWithDecoration(View view) {
mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
return mTmpRect.right;
}
@Override
public int getTransformedStartWithDecoration(View view) {
mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
return mTmpRect.left;
}
@Override
public int getTotalSpace() {
return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
- mLayoutManager.getPaddingRight();
}
@Override
public int getTotalSpaceInOther() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
- mLayoutManager.getPaddingBottom();
}
@Override
public int getEndPadding() {
return mLayoutManager.getPaddingRight();
}
@Override
public int getMode() {
return mLayoutManager.getWidthMode();
}
@Override
public int getModeInOther() {
return mLayoutManager.getHeightMode();
}
};
}
/**
* Creates a vertical OrientationHelper for the given LayoutManager.
*
* @param layoutManager The LayoutManager to attach to.
* @return A new OrientationHelper
*/
public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
return new OrientationHelper(layoutManager) {
@Override
public int getEndAfterPadding() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
}
@Override
public int getEnd() {
return mLayoutManager.getHeight();
}
@Override
public int getStartAfterPadding() {
return mLayoutManager.getPaddingTop();
}
@Override
public int getDecoratedMeasurement(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ params.bottomMargin;
}
@Override
public int getDecoratedMeasurementInOther(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ params.rightMargin;
}
@Override
public int getDecoratedEnd(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
}
@Override
public int getDecoratedStart(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedTop(view) - params.topMargin;
}
@Override
public int getTransformedEndWithDecoration(View view) {
mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
return mTmpRect.bottom;
}
@Override
public int getTransformedStartWithDecoration(View view) {
mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
return mTmpRect.top;
}
@Override
public int getTotalSpace() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
- mLayoutManager.getPaddingBottom();
}
@Override
public int getTotalSpaceInOther() {
return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
- mLayoutManager.getPaddingRight();
}
@Override
public int getEndPadding() {
return mLayoutManager.getPaddingBottom();
}
@Override
public int getMode() {
return mLayoutManager.getHeightMode();
}
@Override
public int getModeInOther() {
return mLayoutManager.getWidthMode();
}
};
}
}

View File

@@ -0,0 +1,51 @@
package com.chwl.app.view.layoutmanager;
import androidx.recyclerview.widget.RecyclerView;
/**
* The implementation will snap the center of the target child view to the center of
* the attached {@link RecyclerView}. And per Child per fling.
*/
public class PageSnapHelper extends CenterSnapHelper {
@Override
public boolean onFling(int velocityX, int velocityY) {
ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager) mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
if (!layoutManager.getInfinite() &&
(layoutManager.mOffset == layoutManager.getMaxOffset()
|| layoutManager.mOffset == layoutManager.getMinOffset())) {
return false;
}
final int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
mGravityScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL
&& Math.abs(velocityY) > minFlingVelocity) {
final int currentPosition = layoutManager.getCurrentPositionOffset();
final int offsetPosition = mGravityScroller.getFinalY() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0;
ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
-currentPosition - offsetPosition : currentPosition + offsetPosition);
return true;
} else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL
&& Math.abs(velocityX) > minFlingVelocity) {
final int currentPosition = layoutManager.getCurrentPositionOffset();
final int offsetPosition = mGravityScroller.getFinalX() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0;
ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
-currentPosition - offsetPosition : currentPosition + offsetPosition);
return true;
}
return true;
}
}

View File

@@ -0,0 +1,178 @@
package com.chwl.app.view.layoutmanager;
import android.content.Context;
import android.view.View;
/**
* An implementation of {@link ViewPagerLayoutManager}
* which rotates items
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class RotateLayoutManager extends ViewPagerLayoutManager {
private int itemSpace;
private float angle;
private float moveSpeed;
private boolean reverseRotate;
public RotateLayoutManager(Context context, int itemSpace) {
this(new Builder(context, itemSpace));
}
public RotateLayoutManager(Context context, int itemSpace, int orientation) {
this(new Builder(context, itemSpace).setOrientation(orientation));
}
public RotateLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
}
public RotateLayoutManager(Builder builder) {
this(builder.context, builder.itemSpace, builder.angle, builder.orientation, builder.moveSpeed,
builder.reverseRotate, builder.maxVisibleItemCount, builder.distanceToBottom,
builder.reverseLayout);
}
private RotateLayoutManager(Context context, int itemSpace, float angle, int orientation, float moveSpeed,
boolean reverseRotate, int maxVisibleItemCount, int distanceToBottom,
boolean reverseLayout) {
super(context, orientation, reverseLayout);
setDistanceToBottom(distanceToBottom);
setMaxVisibleItemCount(maxVisibleItemCount);
this.itemSpace = itemSpace;
this.angle = angle;
this.moveSpeed = moveSpeed;
this.reverseRotate = reverseRotate;
}
public int getItemSpace() {
return itemSpace;
}
public float getAngle() {
return angle;
}
public float getMoveSpeed() {
return moveSpeed;
}
public boolean getReverseRotate() {
return reverseRotate;
}
public void setItemSpace(int itemSpace) {
assertNotInLayoutOrScroll(null);
if (this.itemSpace == itemSpace) return;
this.itemSpace = itemSpace;
removeAllViews();
}
public void setAngle(float centerScale) {
assertNotInLayoutOrScroll(null);
if (this.angle == centerScale) return;
this.angle = centerScale;
requestLayout();
}
public void setMoveSpeed(float moveSpeed) {
assertNotInLayoutOrScroll(null);
if (this.moveSpeed == moveSpeed) return;
this.moveSpeed = moveSpeed;
}
public void setReverseRotate(boolean reverseRotate) {
assertNotInLayoutOrScroll(null);
if (this.reverseRotate == reverseRotate) return;
this.reverseRotate = reverseRotate;
requestLayout();
}
@Override
protected float setInterval() {
return mDecoratedMeasurement + itemSpace;
}
@Override
protected void setItemViewProperty(View itemView, float targetOffset) {
itemView.setRotation(calRotation(targetOffset));
}
@Override
protected float getDistanceRatio() {
if (moveSpeed == 0) return Float.MAX_VALUE;
return 1 / moveSpeed;
}
private float calRotation(float targetOffset) {
final float realAngle = reverseRotate ? angle : -angle;
return realAngle / mInterval * targetOffset;
}
public static class Builder {
private static float INTERVAL_ANGLE = 360f;
private static final float DEFAULT_SPEED = 1f;
private int itemSpace;
private int orientation;
private float angle;
private float moveSpeed;
private boolean reverseRotate;
private boolean reverseLayout;
private Context context;
private int maxVisibleItemCount;
private int distanceToBottom;
public Builder(Context context, int itemSpace) {
this.context = context;
this.itemSpace = itemSpace;
orientation = HORIZONTAL;
angle = INTERVAL_ANGLE;
this.moveSpeed = DEFAULT_SPEED;
reverseRotate = false;
reverseLayout = false;
distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
}
public Builder setOrientation(int orientation) {
this.orientation = orientation;
return this;
}
public Builder setAngle(float angle) {
this.angle = angle;
return this;
}
public Builder setReverseLayout(boolean reverseLayout) {
this.reverseLayout = reverseLayout;
return this;
}
public Builder setMoveSpeed(float moveSpeed) {
this.moveSpeed = moveSpeed;
return this;
}
public Builder setReverseRotate(boolean reverseRotate) {
this.reverseRotate = reverseRotate;
return this;
}
public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
this.maxVisibleItemCount = maxVisibleItemCount;
return this;
}
public Builder setDistanceToBottom(int distanceToBottom) {
this.distanceToBottom = distanceToBottom;
return this;
}
public RotateLayoutManager build() {
return new RotateLayoutManager(this);
}
}
}

View File

@@ -0,0 +1,221 @@
package com.chwl.app.view.layoutmanager;
import android.content.Context;
import android.view.View;
/**
* An implementation of {@link ViewPagerLayoutManager}
* which zooms the center item
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class ScaleLayoutManager extends ViewPagerLayoutManager {
private int itemSpace;
private float minScale;
private float moveSpeed;
private float maxAlpha;
private float minAlpha;
public ScaleLayoutManager(Context context, int itemSpace) {
this(new Builder(context, itemSpace));
}
public ScaleLayoutManager(Context context, int itemSpace, int orientation) {
this(new Builder(context, itemSpace).setOrientation(orientation));
}
public ScaleLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
}
public ScaleLayoutManager(Builder builder) {
this(builder.context, builder.itemSpace, builder.minScale, builder.maxAlpha, builder.minAlpha,
builder.orientation, builder.moveSpeed, builder.maxVisibleItemCount, builder.distanceToBottom,
builder.reverseLayout);
}
private ScaleLayoutManager(Context context, int itemSpace, float minScale, float maxAlpha, float minAlpha,
int orientation, float moveSpeed, int maxVisibleItemCount, int distanceToBottom,
boolean reverseLayout) {
super(context, orientation, reverseLayout);
setDistanceToBottom(distanceToBottom);
setMaxVisibleItemCount(maxVisibleItemCount);
this.itemSpace = itemSpace;
this.minScale = minScale;
this.moveSpeed = moveSpeed;
this.maxAlpha = maxAlpha;
this.minAlpha = minAlpha;
}
public int getItemSpace() {
return itemSpace;
}
public float getMinScale() {
return minScale;
}
public float getMoveSpeed() {
return moveSpeed;
}
public float getMaxAlpha() {
return maxAlpha;
}
public float getMinAlpha() {
return minAlpha;
}
public void setItemSpace(int itemSpace) {
assertNotInLayoutOrScroll(null);
if (this.itemSpace == itemSpace) return;
this.itemSpace = itemSpace;
removeAllViews();
}
public void setMinScale(float minScale) {
assertNotInLayoutOrScroll(null);
if (this.minScale == minScale) return;
this.minScale = minScale;
removeAllViews();
}
public void setMaxAlpha(float maxAlpha) {
assertNotInLayoutOrScroll(null);
if (maxAlpha > 1) maxAlpha = 1;
if (this.maxAlpha == maxAlpha) return;
this.maxAlpha = maxAlpha;
requestLayout();
}
public void setMinAlpha(float minAlpha) {
assertNotInLayoutOrScroll(null);
if (minAlpha < 0) minAlpha = 0;
if (this.minAlpha == minAlpha) return;
this.minAlpha = minAlpha;
requestLayout();
}
public void setMoveSpeed(float moveSpeed) {
assertNotInLayoutOrScroll(null);
if (this.moveSpeed == moveSpeed) return;
this.moveSpeed = moveSpeed;
}
@Override
protected float setInterval() {
return itemSpace + mDecoratedMeasurement;
}
@Override
protected void setItemViewProperty(View itemView, float targetOffset) {
float scale = calculateScale(targetOffset + mSpaceMain);
itemView.setScaleX(scale);
itemView.setScaleY(scale);
final float alpha = calAlpha(targetOffset);
itemView.setAlpha(alpha);
}
private float calAlpha(float targetOffset) {
final float offset = Math.abs(targetOffset);
float alpha = (minAlpha - maxAlpha) / mInterval * offset + maxAlpha;
if (offset >= mInterval) alpha = minAlpha;
return alpha;
}
@Override
protected float getDistanceRatio() {
if (moveSpeed == 0) return Float.MAX_VALUE;
return 1 / moveSpeed;
}
/**
* @param x start positon of the view you want scale
* @return the scale rate of current scroll mOffset
*/
private float calculateScale(float x) {
float deltaX = Math.abs(x - mSpaceMain);
if (deltaX - mDecoratedMeasurement > 0) deltaX = mDecoratedMeasurement;
return 1f - deltaX / mDecoratedMeasurement * (1f - minScale);
}
public static class Builder {
private static final float SCALE_RATE = 0.8f;
private static final float DEFAULT_SPEED = 1f;
private static float MIN_ALPHA = 1f;
private static float MAX_ALPHA = 1f;
private int itemSpace;
private int orientation;
private float minScale;
private float moveSpeed;
private float maxAlpha;
private float minAlpha;
private boolean reverseLayout;
private Context context;
private int maxVisibleItemCount;
private int distanceToBottom;
public Builder(Context context, int itemSpace) {
this.itemSpace = itemSpace;
this.context = context;
orientation = HORIZONTAL;
minScale = SCALE_RATE;
this.moveSpeed = DEFAULT_SPEED;
maxAlpha = MAX_ALPHA;
minAlpha = MIN_ALPHA;
reverseLayout = false;
distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
}
public Builder setOrientation(int orientation) {
this.orientation = orientation;
return this;
}
public Builder setMinScale(float minScale) {
this.minScale = minScale;
return this;
}
public Builder setReverseLayout(boolean reverseLayout) {
this.reverseLayout = reverseLayout;
return this;
}
public Builder setMaxAlpha(float maxAlpha) {
if (maxAlpha > 1) maxAlpha = 1;
this.maxAlpha = maxAlpha;
return this;
}
public Builder setMinAlpha(float minAlpha) {
if (minAlpha < 0) minAlpha = 0;
this.minAlpha = minAlpha;
return this;
}
public Builder setMoveSpeed(float moveSpeed) {
this.moveSpeed = moveSpeed;
return this;
}
public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
this.maxVisibleItemCount = maxVisibleItemCount;
return this;
}
public Builder setDistanceToBottom(int distanceToBottom) {
this.distanceToBottom = distanceToBottom;
return this;
}
public ScaleLayoutManager build() {
return new ScaleLayoutManager(this);
}
}
}

View File

@@ -0,0 +1,32 @@
package com.chwl.app.view.layoutmanager;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
public class ScrollHelper {
/* package */ static void scrollBy(RecyclerView recyclerView, ViewPagerLayoutManager viewPagerLayoutManager, int delta) {
if (viewPagerLayoutManager.getOrientation() == ViewPagerLayoutManager.VERTICAL) {
recyclerView.scrollBy(0, delta);
} else {
recyclerView.scrollBy(delta, 0);
}
}
/* package */ static void smoothScrollToPosition(RecyclerView recyclerView, ViewPagerLayoutManager viewPagerLayoutManager, int targetPosition) {
final int delta = viewPagerLayoutManager.getOffsetToPosition(targetPosition);
if (viewPagerLayoutManager.getOrientation() == ViewPagerLayoutManager.VERTICAL) {
recyclerView.smoothScrollBy(0, delta);
} else {
recyclerView.smoothScrollBy(delta, 0);
}
}
public static void smoothScrollToTargetView(RecyclerView recyclerView, View targetView) {
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (!(layoutManager instanceof ViewPagerLayoutManager)) return;
final int targetPosition = ((ViewPagerLayoutManager) layoutManager).getLayoutPositionOfView(targetView);
smoothScrollToPosition(recyclerView, (ViewPagerLayoutManager) layoutManager, targetPosition);
}
}

View File

@@ -0,0 +1,973 @@
package com.chwl.app.view.layoutmanager;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
/**
* An implementation of {@link RecyclerView.LayoutManager} which behaves like view pager.
* Please make sure your child view have the same size.
*/
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"})
public abstract class ViewPagerLayoutManager extends LinearLayoutManager {
public static final int DETERMINE_BY_MAX_AND_MIN = -1;
public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
public static final int VERTICAL = OrientationHelper.VERTICAL;
private static final int DIRECTION_NO_WHERE = -1;
private static final int DIRECTION_FORWARD = 0;
private static final int DIRECTION_BACKWARD = 1;
protected static final int INVALID_SIZE = Integer.MAX_VALUE;
private SparseArray<View> positionCache = new SparseArray<>();
protected int mDecoratedMeasurement;
protected int mDecoratedMeasurementInOther;
/**
* Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
*/
int mOrientation;
protected int mSpaceMain;
protected int mSpaceInOther;
/**
* The offset of property which will change while scrolling
*/
protected float mOffset;
/**
* Many calculations are made depending on orientation. To keep it clean, this interface
* helps {@link LinearLayoutManager} make those decisions.
* Based on {@link #mOrientation}, an implementation is lazily created in
* {@link #ensureLayoutState} method.
*/
protected OrientationHelper mOrientationHelper;
/**
* Defines if layout should be calculated from end to start.
*/
private boolean mReverseLayout = false;
/**
* This keeps the final value for how LayoutManager should start laying out views.
* It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
* {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run.
*/
private boolean mShouldReverseLayout = false;
/**
* Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
* see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
*/
private boolean mSmoothScrollbarEnabled = true;
/**
* When LayoutManager needs to scroll to a position, it sets this variable and requests a
* layout which will check this variable and re-layout accordingly.
*/
private int mPendingScrollPosition = NO_POSITION;
private SavedState mPendingSavedState = null;
protected float mInterval; //the mInterval of each item's mOffset
/* package */ OnPageChangeListener onPageChangeListener;
private boolean mRecycleChildrenOnDetach;
private boolean mInfinite = false;
private boolean mEnableBringCenterToFront;
private int mLeftItems;
private int mRightItems;
/**
* max visible item count
*/
private int mMaxVisibleItemCount = DETERMINE_BY_MAX_AND_MIN;
private Interpolator mSmoothScrollInterpolator;
private int mDistanceToBottom = INVALID_SIZE;
/**
* use for handle focus
*/
private View currentFocusView;
/**
* @return the mInterval of each item's mOffset
*/
protected abstract float setInterval();
protected abstract void setItemViewProperty(View itemView, float targetOffset);
/**
* cause elevation is not support below api 21,
* so you can set your elevation here for supporting it below api 21
* or you can just setElevation in {@link #setItemViewProperty(View, float)}
*/
protected float setViewElevation(View itemView, float targetOffset) {
return 0;
}
/**
* Creates a horizontal ViewPagerLayoutManager
*/
public ViewPagerLayoutManager(Context context) {
this(context, HORIZONTAL, false);
}
/**
* @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
* @param reverseLayout When set to true, layouts from end to start
*/
public ViewPagerLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context);
setOrientation(orientation);
setReverseLayout(reverseLayout);
setAutoMeasureEnabled(true);
setItemPrefetchEnabled(false);
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* Returns whether LayoutManager will recycle its children when it is detached from
* RecyclerView.
*
* @return true if LayoutManager will recycle its children when it is detached from
* RecyclerView.
*/
public boolean getRecycleChildrenOnDetach() {
return mRecycleChildrenOnDetach;
}
/**
* Set whether LayoutManager will recycle its children when it is detached from
* RecyclerView.
* <p>
* If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
* this flag to <code>true</code> so that views will be available to other RecyclerViews
* immediately.
* <p>
* Note that, setting this flag will result in a performance drop if RecyclerView
* is restored.
*
* @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
*/
public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
mRecycleChildrenOnDetach = recycleChildrenOnDetach;
}
@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler);
if (mRecycleChildrenOnDetach) {
removeAndRecycleAllViews(recycler);
recycler.clear();
}
}
@Override
public Parcelable onSaveInstanceState() {
if (mPendingSavedState != null) {
return new SavedState(mPendingSavedState);
}
SavedState savedState = new SavedState();
savedState.position = mPendingScrollPosition;
savedState.offset = mOffset;
savedState.isReverseLayout = mShouldReverseLayout;
return savedState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof SavedState) {
mPendingSavedState = new SavedState((SavedState) state);
requestLayout();
}
}
/**
* @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
*/
@Override
public boolean canScrollHorizontally() {
return mOrientation == HORIZONTAL;
}
/**
* @return true if {@link #getOrientation()} is {@link #VERTICAL}
*/
@Override
public boolean canScrollVertically() {
return mOrientation == VERTICAL;
}
/**
* Returns the current orientation of the layout.
*
* @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL}
* @see #setOrientation(int)
*/
public int getOrientation() {
return mOrientation;
}
/**
* Sets the orientation of the layout. {@link ViewPagerLayoutManager}
* will do its best to keep scroll position.
*
* @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
*/
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL && orientation != VERTICAL) {
throw new IllegalArgumentException("invalid orientation:" + orientation);
}
assertNotInLayoutOrScroll(null);
if (orientation == mOrientation) {
return;
}
mOrientation = orientation;
mOrientationHelper = null;
mDistanceToBottom = INVALID_SIZE;
removeAllViews();
}
/**
* Returns the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now
* And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range
*
* @return Max visible item count
*/
public int getMaxVisibleItemCount() {
return mMaxVisibleItemCount;
}
/**
* Set the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now
* And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range
*
* @param mMaxVisibleItemCount Max visible item count
*/
public void setMaxVisibleItemCount(int mMaxVisibleItemCount) {
assertNotInLayoutOrScroll(null);
if (this.mMaxVisibleItemCount == mMaxVisibleItemCount) return;
this.mMaxVisibleItemCount = mMaxVisibleItemCount;
removeAllViews();
}
/**
* Calculates the view layout order. (e.g. from end to start or start to end)
* RTL layout support is applied automatically. So if layout is RTL and
* {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
*/
private void resolveShouldLayoutReverse() {
// A == B is the same result, but we rather keep it readable
if (mOrientation == VERTICAL || !isLayoutRTL()) {
mShouldReverseLayout = mReverseLayout;
} else {
mShouldReverseLayout = !mReverseLayout;
}
}
/**
* Returns if views are laid out from the opposite direction of the layout.
*
* @return If layout is reversed or not.
* @see #setReverseLayout(boolean)
*/
public boolean getReverseLayout() {
return mReverseLayout;
}
/**
* Used to reverse item traversal and layout order.
* This behaves similar to the layout change for RTL views. When set to true, first item is
* laid out at the end of the UI, second item is laid out before it etc.
* <p>
* For horizontal layouts, it depends on the layout direction.
* When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will
* layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout
* from LTR.
*/
public void setReverseLayout(boolean reverseLayout) {
assertNotInLayoutOrScroll(null);
if (reverseLayout == mReverseLayout) {
return;
}
mReverseLayout = reverseLayout;
removeAllViews();
}
public void setSmoothScrollInterpolator(Interpolator smoothScrollInterpolator) {
this.mSmoothScrollInterpolator = smoothScrollInterpolator;
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
final int offsetPosition;
// fix wrong scroll direction when infinite enable
if (mInfinite) {
final int currentPosition = getCurrentPosition();
final int total = getItemCount();
final int targetPosition;
if (position < currentPosition) {
int d1 = currentPosition - position;
int d2 = total - currentPosition + position;
targetPosition = d1 < d2 ? (currentPosition - d1) : (currentPosition + d2);
} else {
int d1 = position - currentPosition;
int d2 = currentPosition + total - position;
targetPosition = d1 < d2 ? (currentPosition + d1) : (currentPosition - d2);
}
offsetPosition = getOffsetToPosition(targetPosition);
} else {
offsetPosition = getOffsetToPosition(position);
}
if (mOrientation == VERTICAL) {
recyclerView.smoothScrollBy(0, offsetPosition, mSmoothScrollInterpolator);
} else {
recyclerView.smoothScrollBy(offsetPosition, 0, mSmoothScrollInterpolator);
}
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
mOffset = 0;
return;
}
ensureLayoutState();
resolveShouldLayoutReverse();
//make sure properties are correct while measure more than once
View scrap = getMeasureView(recycler, state, 0);
if (scrap == null) {
removeAndRecycleAllViews(recycler);
mOffset = 0;
return;
}
measureChildWithMargins(scrap, 0, 0);
mDecoratedMeasurement = mOrientationHelper.getDecoratedMeasurement(scrap);
mDecoratedMeasurementInOther = mOrientationHelper.getDecoratedMeasurementInOther(scrap);
mSpaceMain = (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2;
if (mDistanceToBottom == INVALID_SIZE) {
mSpaceInOther = (mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2;
} else {
mSpaceInOther = mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther - mDistanceToBottom;
}
mInterval = setInterval();
setUp();
if (mInterval == 0) {
mLeftItems = 1;
mRightItems = 1;
} else {
mLeftItems = (int) Math.abs(minRemoveOffset() / mInterval) + 1;
mRightItems = (int) Math.abs(maxRemoveOffset() / mInterval) + 1;
}
if (mPendingSavedState != null) {
mShouldReverseLayout = mPendingSavedState.isReverseLayout;
mPendingScrollPosition = mPendingSavedState.position;
mOffset = mPendingSavedState.offset;
}
if (mPendingScrollPosition != NO_POSITION) {
mOffset = mShouldReverseLayout ?
mPendingScrollPosition * -mInterval : mPendingScrollPosition * mInterval;
}
layoutItems(recycler);
}
private View getMeasureView(RecyclerView.Recycler recycler, RecyclerView.State state, int index) {
if (index >= state.getItemCount() || index < 0) return null;
try {
return recycler.getViewForPosition(index);
} catch (Exception e) {
return getMeasureView(recycler, state, index + 1);
}
}
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
mPendingSavedState = null;
mPendingScrollPosition = NO_POSITION;
}
@Override
public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) {
final int currentPosition = getCurrentPosition();
final View currentView = findViewByPosition(currentPosition);
if (currentView == null) return true;
if (recyclerView.hasFocus()) {
final int movement = getMovement(direction);
if (movement != DIRECTION_NO_WHERE) {
final int targetPosition = movement == DIRECTION_BACKWARD ?
currentPosition - 1 : currentPosition + 1;
ScrollHelper.smoothScrollToPosition(recyclerView, this, targetPosition);
}
} else {
currentView.addFocusables(views, direction, focusableMode);
}
return true;
}
@Override
public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {
return null;
}
private int getMovement(int direction) {
if (mOrientation == VERTICAL) {
if (direction == View.FOCUS_UP) {
return mShouldReverseLayout ? DIRECTION_FORWARD : DIRECTION_BACKWARD;
} else if (direction == View.FOCUS_DOWN) {
return mShouldReverseLayout ? DIRECTION_BACKWARD : DIRECTION_FORWARD;
} else {
return DIRECTION_NO_WHERE;
}
} else {
if (direction == View.FOCUS_LEFT) {
return mShouldReverseLayout ? DIRECTION_FORWARD : DIRECTION_BACKWARD;
} else if (direction == View.FOCUS_RIGHT) {
return mShouldReverseLayout ? DIRECTION_BACKWARD : DIRECTION_FORWARD;
} else {
return DIRECTION_NO_WHERE;
}
}
}
void ensureLayoutState() {
if (mOrientationHelper == null) {
mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
}
}
/**
* You can set up your own properties here or change the exist properties like mSpaceMain and mSpaceInOther
*/
protected void setUp() {
}
private float getProperty(int position) {
return mShouldReverseLayout ? position * -mInterval : position * mInterval;
}
@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
removeAllViews();
mOffset = 0;
}
@Override
public void scrollToPosition(int position) {
if (!mInfinite && (position < 0 || position >= getItemCount())) return;
mPendingScrollPosition = position;
mOffset = mShouldReverseLayout ? position * -mInterval : position * mInterval;
requestLayout();
}
@Override
public int computeHorizontalScrollOffset(RecyclerView.State state) {
return computeScrollOffset();
}
@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
return computeScrollOffset();
}
@Override
public int computeHorizontalScrollExtent(RecyclerView.State state) {
return computeScrollExtent();
}
@Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
return computeScrollExtent();
}
@Override
public int computeHorizontalScrollRange(RecyclerView.State state) {
return computeScrollRange();
}
@Override
public int computeVerticalScrollRange(RecyclerView.State state) {
return computeScrollRange();
}
private int computeScrollOffset() {
if (getChildCount() == 0) {
return 0;
}
if (!mSmoothScrollbarEnabled) {
return !mShouldReverseLayout ?
getCurrentPosition() : getItemCount() - getCurrentPosition() - 1;
}
final float realOffset = getOffsetOfRightAdapterPosition();
return !mShouldReverseLayout ? (int) realOffset : (int) ((getItemCount() - 1) * mInterval + realOffset);
}
private int computeScrollExtent() {
if (getChildCount() == 0) {
return 0;
}
if (!mSmoothScrollbarEnabled) {
return 1;
}
return (int) mInterval;
}
private int computeScrollRange() {
if (getChildCount() == 0) {
return 0;
}
if (!mSmoothScrollbarEnabled) {
return getItemCount();
}
return (int) (getItemCount() * mInterval);
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
private int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
ensureLayoutState();
int willScroll = dy;
float realDx = dy / getDistanceRatio();
if (Math.abs(realDx) < 0.00000001f) {
return 0;
}
float targetOffset = mOffset + realDx;
//handle the boundary
if (!mInfinite && targetOffset < getMinOffset()) {
willScroll -= (targetOffset - getMinOffset()) * getDistanceRatio();
} else if (!mInfinite && targetOffset > getMaxOffset()) {
willScroll = (int) ((getMaxOffset() - mOffset) * getDistanceRatio());
}
realDx = willScroll / getDistanceRatio();
mOffset += realDx;
//handle recycle
layoutItems(recycler);
return willScroll;
}
private void layoutItems(RecyclerView.Recycler recycler) {
detachAndScrapAttachedViews(recycler);
positionCache.clear();
final int itemCount = getItemCount();
if (itemCount == 0) return;
// make sure that current position start from 0 to 1
final int currentPos = mShouldReverseLayout ?
-getCurrentPositionOffset() : getCurrentPositionOffset();
int start = currentPos - mLeftItems;
int end = currentPos + mRightItems;
// handle max visible count
if (useMaxVisibleCount()) {
boolean isEven = mMaxVisibleItemCount % 2 == 0;
if (isEven) {
int offset = mMaxVisibleItemCount / 2;
start = currentPos - offset + 1;
end = currentPos + offset + 1;
} else {
int offset = (mMaxVisibleItemCount - 1) / 2;
start = currentPos - offset;
end = currentPos + offset + 1;
}
}
if (!mInfinite) {
if (start < 0) {
start = 0;
if (useMaxVisibleCount()) end = mMaxVisibleItemCount;
}
if (end > itemCount) end = itemCount;
}
float lastOrderWeight = Float.MIN_VALUE;
for (int i = start; i < end; i++) {
if (useMaxVisibleCount() || !removeCondition(getProperty(i) - mOffset)) {
// start and end base on current position,
// so we need to calculate the adapter position
int adapterPosition = i;
if (i >= itemCount) {
adapterPosition %= itemCount;
} else if (i < 0) {
int delta = (-adapterPosition) % itemCount;
if (delta == 0) delta = itemCount;
adapterPosition = itemCount - delta;
}
final View scrap = recycler.getViewForPosition(adapterPosition);
measureChildWithMargins(scrap, 0, 0);
resetViewProperty(scrap);
// we need i to calculate the real offset of current view
final float targetOffset = getProperty(i) - mOffset;
layoutScrap(scrap, targetOffset);
final float orderWeight = mEnableBringCenterToFront ?
setViewElevation(scrap, targetOffset) : adapterPosition;
if (orderWeight > lastOrderWeight) {
addView(scrap);
} else {
addView(scrap, 0);
}
if (i == currentPos) currentFocusView = scrap;
lastOrderWeight = orderWeight;
positionCache.put(i, scrap);
}
}
currentFocusView.requestFocus();
}
private boolean useMaxVisibleCount() {
return mMaxVisibleItemCount != DETERMINE_BY_MAX_AND_MIN;
}
private boolean removeCondition(float targetOffset) {
return targetOffset > maxRemoveOffset() || targetOffset < minRemoveOffset();
}
private void resetViewProperty(View v) {
v.setRotation(0);
v.setRotationY(0);
v.setRotationX(0);
v.setScaleX(1f);
v.setScaleY(1f);
v.setAlpha(1f);
}
/* package */ float getMaxOffset() {
return !mShouldReverseLayout ? (getItemCount() - 1) * mInterval : 0;
}
/* package */ float getMinOffset() {
return !mShouldReverseLayout ? 0 : -(getItemCount() - 1) * mInterval;
}
private void layoutScrap(View scrap, float targetOffset) {
final int left = calItemLeft(scrap, targetOffset);
final int top = calItemTop(scrap, targetOffset);
if (mOrientation == VERTICAL) {
layoutDecorated(scrap, mSpaceInOther + left, mSpaceMain + top,
mSpaceInOther + left + mDecoratedMeasurementInOther, mSpaceMain + top + mDecoratedMeasurement);
} else {
layoutDecorated(scrap, mSpaceMain + left, mSpaceInOther + top,
mSpaceMain + left + mDecoratedMeasurement, mSpaceInOther + top + mDecoratedMeasurementInOther);
}
setItemViewProperty(scrap, targetOffset);
}
protected int calItemLeft(View itemView, float targetOffset) {
return mOrientation == VERTICAL ? 0 : (int) targetOffset;
}
protected int calItemTop(View itemView, float targetOffset) {
return mOrientation == VERTICAL ? (int) targetOffset : 0;
}
/**
* when the target offset reach this,
* the view will be removed and recycled in {@link #layoutItems(RecyclerView.Recycler)}
*/
protected float maxRemoveOffset() {
return mOrientationHelper.getTotalSpace() - mSpaceMain;
}
/**
* when the target offset reach this,
* the view will be removed and recycled in {@link #layoutItems(RecyclerView.Recycler)}
*/
protected float minRemoveOffset() {
return -mDecoratedMeasurement - mOrientationHelper.getStartAfterPadding() - mSpaceMain;
}
protected float getDistanceRatio() {
return 1f;
}
public int getCurrentPosition() {
if (getItemCount() == 0) return 0;
int position = getCurrentPositionOffset();
if (!mInfinite) return Math.abs(position);
position = !mShouldReverseLayout ?
//take care of position = getItemCount()
(position >= 0 ?
position % getItemCount() :
getItemCount() + position % getItemCount()) :
(position > 0 ?
getItemCount() - position % getItemCount() :
-position % getItemCount());
return position == getItemCount() ? 0 : position;
}
@Override
public View findViewByPosition(int position) {
final int itemCount = getItemCount();
if (itemCount == 0) return null;
for (int i = 0; i < positionCache.size(); i++) {
final int key = positionCache.keyAt(i);
if (key >= 0) {
if (position == key % itemCount) return positionCache.valueAt(i);
} else {
int delta = key % itemCount;
if (delta == 0) delta = -itemCount;
if (itemCount + delta == position) return positionCache.valueAt(i);
}
}
return null;
}
public int getLayoutPositionOfView(View v) {
for (int i = 0; i < positionCache.size(); i++) {
int key = positionCache.keyAt(i);
View value = positionCache.get(key);
if (value == v) return key;
}
return -1;
}
/* package */ int getCurrentPositionOffset() {
if (mInterval == 0) return 0;
return Math.round(mOffset / mInterval);
}
/**
* Sometimes we need to get the right offset of matching adapter position
* cause when {@link #mInfinite} is set true, there will be no limitation of {@link #mOffset}
*/
private float getOffsetOfRightAdapterPosition() {
if (mShouldReverseLayout)
return mInfinite ?
(mOffset <= 0 ?
(mOffset % (mInterval * getItemCount())) :
(getItemCount() * -mInterval + mOffset % (mInterval * getItemCount()))) :
mOffset;
else
return mInfinite ?
(mOffset >= 0 ?
(mOffset % (mInterval * getItemCount())) :
(getItemCount() * mInterval + mOffset % (mInterval * getItemCount()))) :
mOffset;
}
/**
* used by {@link CenterSnapHelper} to center the current view
*
* @return the dy between center and current position
*/
public int getOffsetToCenter() {
if (mInfinite)
return (int) ((getCurrentPositionOffset() * mInterval - mOffset) * getDistanceRatio());
return (int) ((getCurrentPosition() *
(!mShouldReverseLayout ? mInterval : -mInterval) - mOffset) * getDistanceRatio());
}
public int getOffsetToPosition(int position) {
if (mInfinite)
return (int) (((getCurrentPositionOffset() +
(!mShouldReverseLayout ? position - getCurrentPositionOffset() : -getCurrentPositionOffset() - position)) *
mInterval - mOffset) * getDistanceRatio());
return (int) ((position *
(!mShouldReverseLayout ? mInterval : -mInterval) - mOffset) * getDistanceRatio());
}
public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
this.onPageChangeListener = onPageChangeListener;
}
public void setInfinite(boolean enable) {
assertNotInLayoutOrScroll(null);
if (enable == mInfinite) {
return;
}
mInfinite = enable;
requestLayout();
}
public boolean getInfinite() {
return mInfinite;
}
public int getDistanceToBottom() {
return mDistanceToBottom == INVALID_SIZE ?
(mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2 : mDistanceToBottom;
}
public void setDistanceToBottom(int mDistanceToBottom) {
assertNotInLayoutOrScroll(null);
if (this.mDistanceToBottom == mDistanceToBottom) return;
this.mDistanceToBottom = mDistanceToBottom;
removeAllViews();
}
/**
* When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
* based on the number of visible pixels in the visible items. This however assumes that all
* list items have similar or equal widths or heights (depending on list orientation).
* If you use a list in which items have different dimensions, the scrollbar will change
* appearance as the user scrolls through the list. To avoid this issue, you need to disable
* this property.
* <p>
* When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
* solely on the number of items in the adapter and the position of the visible items inside
* the adapter. This provides a stable scrollbar as the user navigates through a list of items
* with varying widths / heights.
*
* @param enabled Whether or not to enable smooth scrollbar.
* @see #setSmoothScrollbarEnabled(boolean)
*/
public void setSmoothScrollbarEnabled(boolean enabled) {
mSmoothScrollbarEnabled = enabled;
}
public void setEnableBringCenterToFront(boolean bringCenterToTop) {
assertNotInLayoutOrScroll(null);
if (mEnableBringCenterToFront == bringCenterToTop) {
return;
}
this.mEnableBringCenterToFront = bringCenterToTop;
requestLayout();
}
public boolean getEnableBringCenterToFront() {
return mEnableBringCenterToFront;
}
/**
* Returns the current state of the smooth scrollbar feature. It is enabled by default.
*
* @return True if smooth scrollbar is enabled, false otherwise.
* @see #setSmoothScrollbarEnabled(boolean)
*/
public boolean getSmoothScrollbarEnabled() {
return mSmoothScrollbarEnabled;
}
private static class SavedState implements Parcelable {
int position;
float offset;
boolean isReverseLayout;
SavedState() {
}
SavedState(Parcel in) {
position = in.readInt();
offset = in.readFloat();
isReverseLayout = in.readInt() == 1;
}
public SavedState(SavedState other) {
position = other.position;
offset = other.offset;
isReverseLayout = other.isReverseLayout;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(position);
dest.writeFloat(offset);
dest.writeInt(isReverseLayout ? 1 : 0);
}
public static final Creator<SavedState> CREATOR
= new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public interface OnPageChangeListener {
void onPageSelected(int position);
void onPageScrollStateChanged(int state);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M7.097,1.372C6.777,1.677 6.777,2.171 7.097,2.476L12.882,8L7.097,13.524C6.777,13.829 6.777,14.323 7.097,14.628C7.416,14.933 7.934,14.933 8.254,14.628L14.618,8.552C14.937,8.247 14.937,7.753 14.618,7.448L8.254,1.372C7.934,1.067 7.416,1.067 7.097,1.372Z"
android:strokeAlpha="0.8"
android:strokeWidth="0.516666667"
android:fillColor="#FFFFFF"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"
android:fillAlpha="0.8"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="44dp"
android:height="44dp"
android:viewportWidth="44"
android:viewportHeight="44">
<path
android:pathData="M11.383,15.916C11.383,15.916 13.721,12.309 17.416,10.374C21.114,8.439 25.667,8.995 28.652,10.872C31.64,12.748 32.646,14.332 32.646,14.332L35.219,12.866C35.219,12.866 35.75,12.602 35.75,13.218L35.75,22.426C35.75,22.426 35.75,23.247 35.128,22.952C34.607,22.707 28.955,19.492 27.176,18.478C26.2,18.042 27.057,17.687 27.057,17.687L29.542,16.267C29.542,16.267 28.124,14.492 26.051,13.552C23.834,12.397 21.756,12.259 19.214,13.22C17.553,13.847 15.599,15.452 14.193,17.821L11.383,15.916L11.383,15.916ZM34.451,28.084C34.451,28.084 32.113,31.69 28.417,33.626C24.721,35.562 20.166,35.004 17.181,33.128C14.193,31.251 13.187,29.667 13.187,29.667L10.614,31.134C10.614,31.134 10.083,31.397 10.083,30.783L10.083,21.573C10.083,21.573 10.083,20.754 10.705,21.047C11.226,21.293 16.878,24.507 18.657,25.521C19.633,25.958 18.776,26.313 18.776,26.313L16.291,27.732C16.291,27.732 17.71,29.507 19.782,30.447C21.999,31.602 24.077,31.74 26.62,30.779C28.281,30.152 30.234,28.548 31.64,26.178L34.451,28.084L34.451,28.084Z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillAlpha="0.8"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -1,20 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/star_bg" />
<com.tencent.qgame.animplayer.AnimView
android:id="@+id/animView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@id/recyclerView"
app:layout_constraintDimensionRatio="223:408"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/recyclerView"
app:layout_constraintWidth_percent="0.594"
tools:background="#10000000" />
<com.chwl.app.view.layoutmanager.AutoPlayRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#10000000"
app:direction="left"
app:distance="1"
app:timeInterval="5" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/line_refresh_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="STAR"
android:textSize="40sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:orientation="vertical"
app:layout_constraintGuide_percent="0.797" />
<ImageView
android:id="@+id/iv_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/star_ic_refresh"
app:layout_constraintBottom_toBottomOf="@id/recyclerView"
app:layout_constraintStart_toStartOf="@id/line_refresh_start"
app:layout_constraintTop_toTopOf="@id/recyclerView" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_target_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_10"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/recyclerView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/recyclerView"
app:layout_constraintWidth_percent="0.41"
tools:visibility="visible">
<TextView
android:id="@+id/tv_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_4"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/dp_20"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Name" />
<ImageView
android:id="@+id/iv_follow"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/dp_10"
android:background="@drawable/star_bg_btn"
android:scaleType="centerInside"
android:src="@drawable/star_ic_follow"
app:layout_constraintDimensionRatio="72:48"
app:layout_constraintEnd_toStartOf="@id/iv_chat"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_user_name" />
<ImageView
android:id="@+id/iv_chat"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/dp_8"
android:background="@drawable/star_bg_btn"
android:scaleType="centerInside"
android:src="@drawable/star_ic_chat"
app:layout_constraintDimensionRatio="72:48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_follow"
app:layout_constraintTop_toTopOf="@id/iv_follow" />
<TextView
android:id="@+id/tv_user_desc"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/dp_7"
android:background="@drawable/star_bg_user_desc"
android:ellipsize="end"
android:paddingHorizontal="@dimen/dp_9"
android:paddingVertical="@dimen/dp_11"
android:textColor="@color/white"
android:textSize="@dimen/dp_11"
app:layout_constraintDimensionRatio="154:86"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_follow"
tools:text="Content" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_target"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/star_bg_target"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/recyclerView"
app:layout_constraintDimensionRatio="250:210"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/recyclerView"
app:layout_constraintWidth_percent="0.666"
tools:visibility="visible">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/line_user_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.2" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/line_gift_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.704" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/line_close_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.19" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_user"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="1dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="@id/line_user_start"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.456"
app:shapeAppearance="@style/shape_circle"
app:strokeColor="#FFD667FF"
app:strokeWidth="2dp"
tools:src="@drawable/default_cover" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerInside"
android:src="@drawable/star_ic_close"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/line_close_top"
app:layout_constraintWidth_percent="0.184" />
<com.opensource.svgaplayer.SVGAImageView
android:id="@+id/iv_gift"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="@id/line_gift_start"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.232"
app:loopCount="-1"
tools:src="@drawable/default_cover" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,25 @@
<?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:layout_width="@dimen/dp_64"
android:layout_height="@dimen/dp_64">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/dp_1"
android:src="@color/black"
app:shapeAppearance="@style/shape_circle"
app:strokeColor="#FFD667FF"
app:strokeWidth="@dimen/dp_2" />
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="-"
android:textColor="@color/black"
android:textSize="16sp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,5 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AutoPlayRecyclerView">
<attr name="timeInterval" format="integer" />
<attr name="distance" format="integer" />
<attr name="direction" format="enum">
<enum name="left" value="1" />
<enum name="right" value="2" />
</attr>
</declare-styleable>
<declare-styleable name="StickyScrollView">
<attr name="stuckShadowHeight" format="dimension" />
<attr name="stuckShadowDrawable" format="reference" />

View File

@@ -719,4 +719,6 @@
<color name="color_FE6974">#FE6974</color>
<color name="color_09bb07">#09bb07</color>
<color name="color_D6D6D6">#d6d6d6</color>
<color name="color_D667FF">#FFD667FF</color>
<color name="color_57CDFF">#FF57CDFF</color>
</resources>

View File

@@ -588,10 +588,11 @@
<item name="windowNoTitle">true</item>
</style>
<style name="shape_home_recommend_item">
<style name="shape_home_recommend_item" parent="shape_circle"/>
<style name="shape_circle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
</resources>

View File

@@ -0,0 +1,14 @@
package com.chwl.core.home.bean
import androidx.annotation.Keep
import java.io.Serializable
@Keep
data class StarUser(
val avatar: String? = null,
val gender: Int? = null,
val hasLike: Boolean? = null,
val nick: String? = null,
val uid: Long? = null,
val userDesc: String? = null,
) : Serializable

View File

@@ -94,18 +94,6 @@ object HomeModel : BaseModel() {
}.compose(RxHelper.handleSchAndExce())
}
/**
* 获取首页推荐房间
*/
suspend fun getHomeTabHome(): List<HomeTabMapInfo>? =
launchRequest {
api.apiHomeTabHome(
"", AuthModel.get().currentUid.toString(),
CommunityConstant.VERSION_VALID_TYPE,
AuthModel.get().ticket
)
}
suspend fun getHomeBanner(type: String): List<BannerInfo>? =
launchRequest {
api.apiHomeBanner(
@@ -115,57 +103,11 @@ object HomeModel : BaseModel() {
)
}
//首页改版资源位
suspend fun getCurrentResource(): List<CurrentResourceInfo>? =
launchRequest {
api.getCurrentResource()
}
suspend fun getDiamondGiveHistory(page: Int, pageSize: Int): List<DiamondGiveHistoryInfo>? =
launchRequest {
api.getRecord(page, pageSize)
}
suspend fun getSearchUser(erbanNo: Long): SearchUserInfo? =
launchRequest {
api.getSearchUser(erbanNo)
}
suspend fun giveGift(toUid: Long, giftId: Int, giftNum: Int): String? =
launchRequest {
api.giveGift(toUid, giftId, giftNum)
}
suspend fun getGiveDetail(
uid: Long,
type: Int,
page: Int,
pageSize: Int
): List<DiamondGiveHistoryInfo>? =
launchRequest {
api.getGiveDetail(uid, type, page, pageSize)
}
suspend fun getHotRoom(): List<HomeRoomInfo>? =
launchRequest {
api.getHotRoom()
}
suspend fun getHomeResource(): List<ResourceInfo>? =
launchRequest {
api.getHomeResource()
}
suspend fun getResourceJumpInfo(id: Int): HomeRoomInfo? =
launchRequest {
api.getResourceJumpInfo(id)
}
suspend fun getHomeChatPick(): String? =
launchRequest {
api.getHomeChatPick()
}
suspend fun getCommonRoom(tabId: Int, pageNum: Int, pageSize: Int): List<HomeRoomInfo>? =
launchRequest {
api.getCommonRoom(tabId, pageNum, pageSize)
@@ -197,36 +139,11 @@ object HomeModel : BaseModel() {
api.fastPickGameRoom(mgId)
}
suspend fun getHomeSingleAnchorList(): List<HomeRoomInfo>? =
launchRequest {
api.getHomeSingleAnchorList()
}
suspend fun getMoreSingleAnchorList(id: Long?): List<HomeRoomInfo>? =
launchRequest {
api.getMoreSingleAnchorList(if (id == 0L) null else id)
}
suspend fun requestSingleRoomSortList(): List<SingleRoomSortInfo>? =
launchRequest {
api.requestSingleRoomSortList()
}
suspend fun requestHomeLiveTopInfo(): HomeLiveTopInfo? =
launchRequest {
api.requestHomeLiveTopInfo()
}
suspend fun requestMeCenterInfoList(): List<MeCenterInfo>? =
launchRequest {
api.requestMeCenterInfoList()
}
suspend fun getGameList(): List<GameInfo>? =
launchRequest {
api.getGameList()
}
suspend fun requestAnchorInfo(): AnchorInfo? =
launchRequest {
api.requestAnchorInfo()
@@ -242,6 +159,11 @@ object HomeModel : BaseModel() {
api.getNewFriendList(gender)
}
suspend fun getStarUserList(pageNum: Int, pageSize: Int): List<StarUser>? =
launchRequest {
api.getStarUserList(pageNum, pageSize)
}
private interface Api {
/**
* 提交反馈
@@ -282,23 +204,6 @@ object HomeModel : BaseModel() {
@POST("interactive/unreadCount")
fun getUnreadCount(@Query("uid") uid: Long): Single<ServiceResult<UnReadCountInfo>>
/**
* 首页推荐房间
*
* @param tabId
* @param uid
* @param types
* @param ticket
* @return
*/
@GET("/home/tab/homeV2")
suspend fun apiHomeTabHome(
@Query("tabId") tabId: String,
@Query("uid") uid: String,
@Query("types") types: String,
@Query("ticket") ticket: String
): ServiceResult<List<HomeTabMapInfo>>
/**
* 首页Banner
*
@@ -316,14 +221,6 @@ object HomeModel : BaseModel() {
@Query("ticket") ticket: String
): ServiceResult<List<BannerInfo>>
/**
* 首页改版资源位
*
* @return
*/
@GET("/home/currentResource")
suspend fun getCurrentResource(): ServiceResult<List<CurrentResourceInfo>>
/**
* 首页热门房间
*
@@ -332,30 +229,6 @@ object HomeModel : BaseModel() {
@GET("/home/hotRoom")
suspend fun getHotRoom(): ServiceResult<List<HomeRoomInfo>>
/**
* 首页资源位
*
* @return
*/
@GET("/home/currentResource")
suspend fun getHomeResource(): ServiceResult<List<ResourceInfo>>
/**
* 首页资源位跳转
*
* @return
*/
@GET("/home/pickResource")
suspend fun getResourceJumpInfo(@Query("id") id: Int): ServiceResult<HomeRoomInfo>
/**
* 首页资源位跳转
*
* @return
*/
@GET("home/chat/pick")
suspend fun getHomeChatPick(): ServiceResult<String>
/**
* 首页热门房间
*
@@ -415,14 +288,6 @@ object HomeModel : BaseModel() {
@GET("home/fastPickV2")
suspend fun fastPickGameRoom(@Query("mgId") mgId: Long?): ServiceResult<HomeRoomInfo>
/**
* 首页人气主播
*
* @return -
*/
@GET("/single/broadcast/getMainPopularityAnchorList")
suspend fun getHomeSingleAnchorList(): ServiceResult<List<HomeRoomInfo>>
/**
* 首页人气主播更多二级页面
*
@@ -431,35 +296,6 @@ object HomeModel : BaseModel() {
@GET("/single/broadcast/morePopularityAnchorList")
suspend fun getMoreSingleAnchorList(@Query("singleRoomSortId") id: Long?): ServiceResult<List<HomeRoomInfo>>
/**
* @return
*/
@GET("single/broadcast/sort")
suspend fun requestSingleRoomSortList(): ServiceResult<List<SingleRoomSortInfo>>
/**
* @return
*/
@GET("single/broadcast/onceLook")
suspend fun requestHomeLiveTopInfo(): ServiceResult<HomeLiveTopInfo>
/**
* @return
*/
@GET("personal/center/list")
suspend fun requestMeCenterInfoList(): ServiceResult<List<MeCenterInfo>>
/**
*
*
* @param uid
* @return
*/
@POST("/miniGame/record/miniGameList")
suspend fun getGameList(
): ServiceResult<List<GameInfo>>
/**
* @return
*/
@@ -474,52 +310,6 @@ object HomeModel : BaseModel() {
@GET("/home/playV2")
suspend fun apiHomePlayV2(): ServiceResult<List<HomeRoomInfo>>
/**
* 轉贈鉆石歷史記錄
*
* @return
*/
@GET("/user/diamond/giveRecord")
suspend fun getRecord(
@Query("pageNum") pageNum: Int?,
@Query("pageSize") pageSize: Int?
): ServiceResult<List<DiamondGiveHistoryInfo>>
/**
* 精確搜索用戶
*
* @return
*/
@POST("/user/diamond/searchUser")
suspend fun getSearchUser(
@Query("erbanNo") erbanNo: Long?
): ServiceResult<SearchUserInfo>
/**
* 贈送禮物
*
* @return
*/
@POST("/user/diamond/giveGift")
suspend fun giveGift(
@Query("toUid") toUid: Long?,
@Query("giftId") giftId: Int?,
@Query("giftNum") giftNum: Int?
): ServiceResult<String>
/**
* 轉贈詳情
*
* @return
*/
@GET("/user/diamond/giveRecordVoByType")
suspend fun getGiveDetail(
@Query("toUid") toUid: Long?,
@Query("type") type: Int?,
@Query("pageNum") pageNum: Int?,
@Query("pageSize") pageSize: Int?
): ServiceResult<List<DiamondGiveHistoryInfo>>
/**
* 发现新朋友
*
@@ -528,6 +318,17 @@ object HomeModel : BaseModel() {
@GET("/home/newFriend")
suspend fun getNewFriendList(@Query("gender") gender: Int?): ServiceResult<List<UserInfo>>
/**
* 星球推荐用户列表
*
* @return
*/
@GET("/home/recommendUsers")
suspend fun getStarUserList(
@Query("pageNum") pageNum: Int,
@Query("pageSize") pageSize: Int
): ServiceResult<List<StarUser>>
}
}

View File

@@ -650,7 +650,7 @@ public class FileHelper {
}
if (!file.exists()) {
try {
if (!ensureDirExists(dir)) {
if (!ensureDirExists(file.getParentFile().getAbsolutePath())) {
return false;
}
file.createNewFile();