update:2020.04.22
fix: add:
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
1
RecyclerBanner/.gitignore
vendored
Normal file
1
RecyclerBanner/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
25
RecyclerBanner/build.gradle
Normal file
25
RecyclerBanner/build.gradle
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 27
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 15
|
||||||
|
targetSdkVersion 26
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
|
|
||||||
|
}
|
||||||
21
RecyclerBanner/proguard-rules.pro
vendored
Normal file
21
RecyclerBanner/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
2
RecyclerBanner/src/main/AndroidManifest.xml
Normal file
2
RecyclerBanner/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.library"/>
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package com.example.library.banner;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.core.view.GravityCompat;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.OrientationHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.example.library.R;
|
||||||
|
import com.example.library.banner.layoutmanager.CenterSnapHelper;
|
||||||
|
import com.example.library.banner.layoutmanager.BannerLayoutManager;
|
||||||
|
|
||||||
|
import static android.widget.AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
|
||||||
|
|
||||||
|
public class BannerLayout extends FrameLayout {
|
||||||
|
|
||||||
|
private int autoPlayDuration;//刷新间隔时间
|
||||||
|
|
||||||
|
private boolean showIndicator;//是否显示指示器
|
||||||
|
private RecyclerView indicatorContainer;
|
||||||
|
private Drawable mSelectedDrawable;
|
||||||
|
private Drawable mUnselectedDrawable;
|
||||||
|
private IndicatorAdapter indicatorAdapter;
|
||||||
|
private int indicatorMargin;//指示器间距
|
||||||
|
private RecyclerView mRecyclerView;
|
||||||
|
|
||||||
|
private BannerLayoutManager mLayoutManager;
|
||||||
|
|
||||||
|
private int WHAT_AUTO_PLAY = 1000;
|
||||||
|
|
||||||
|
private boolean hasInit;
|
||||||
|
private int bannerSize = 1;
|
||||||
|
private int currentIndex;
|
||||||
|
private boolean isPlaying = false;
|
||||||
|
|
||||||
|
private boolean isAutoPlaying = true;
|
||||||
|
int itemSpace;
|
||||||
|
float centerScale;
|
||||||
|
float moveSpeed;
|
||||||
|
protected Handler mHandler = new Handler(new Handler.Callback() {
|
||||||
|
@Override
|
||||||
|
public boolean handleMessage(Message msg) {
|
||||||
|
if (msg.what == WHAT_AUTO_PLAY) {
|
||||||
|
if (currentIndex == mLayoutManager.getCurrentPosition()) {
|
||||||
|
++currentIndex;
|
||||||
|
mRecyclerView.smoothScrollToPosition(currentIndex);
|
||||||
|
mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
|
||||||
|
refreshIndicator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public BannerLayout(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BannerLayout(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BannerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initView(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initView(Context context, AttributeSet attrs) {
|
||||||
|
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BannerLayout);
|
||||||
|
showIndicator = a.getBoolean(R.styleable.BannerLayout_showIndicator, true);
|
||||||
|
autoPlayDuration = a.getInt(R.styleable.BannerLayout_interval, 4000);
|
||||||
|
isAutoPlaying = a.getBoolean(R.styleable.BannerLayout_autoPlaying, true);
|
||||||
|
itemSpace = a.getInt(R.styleable.BannerLayout_itemSpace, 20);
|
||||||
|
centerScale = a.getFloat(R.styleable.BannerLayout_centerScale, 1.2f);
|
||||||
|
moveSpeed = a.getFloat(R.styleable.BannerLayout_moveSpeed, 1.0f);
|
||||||
|
if (mSelectedDrawable == null) {
|
||||||
|
//绘制默认选中状态图形
|
||||||
|
GradientDrawable selectedGradientDrawable = new GradientDrawable();
|
||||||
|
selectedGradientDrawable.setShape(GradientDrawable.OVAL);
|
||||||
|
selectedGradientDrawable.setColor(Color.RED);
|
||||||
|
selectedGradientDrawable.setSize(dp2px(5), dp2px(5));
|
||||||
|
selectedGradientDrawable.setCornerRadius(dp2px(5) / 2);
|
||||||
|
mSelectedDrawable = new LayerDrawable(new Drawable[]{selectedGradientDrawable});
|
||||||
|
}
|
||||||
|
if (mUnselectedDrawable == null) {
|
||||||
|
//绘制默认未选中状态图形
|
||||||
|
GradientDrawable unSelectedGradientDrawable = new GradientDrawable();
|
||||||
|
unSelectedGradientDrawable.setShape(GradientDrawable.OVAL);
|
||||||
|
unSelectedGradientDrawable.setColor(Color.GRAY);
|
||||||
|
unSelectedGradientDrawable.setSize(dp2px(5), dp2px(5));
|
||||||
|
unSelectedGradientDrawable.setCornerRadius(dp2px(5) / 2);
|
||||||
|
mUnselectedDrawable = new LayerDrawable(new Drawable[]{unSelectedGradientDrawable});
|
||||||
|
}
|
||||||
|
|
||||||
|
indicatorMargin = dp2px(4);
|
||||||
|
int marginLeft = dp2px(16);
|
||||||
|
int marginRight = dp2px(0);
|
||||||
|
int marginBottom = dp2px(11);
|
||||||
|
int gravity = GravityCompat.START;
|
||||||
|
int o = a.getInt(R.styleable.BannerLayout_orientation, 0);
|
||||||
|
int orientation = 0;
|
||||||
|
if (o == 0) {
|
||||||
|
orientation = OrientationHelper.HORIZONTAL;
|
||||||
|
} else if (o == 1) {
|
||||||
|
orientation = OrientationHelper.VERTICAL;
|
||||||
|
}
|
||||||
|
a.recycle();
|
||||||
|
//轮播图部分
|
||||||
|
mRecyclerView = new RecyclerView(context);
|
||||||
|
LayoutParams vpLayoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
addView(mRecyclerView, vpLayoutParams);
|
||||||
|
mLayoutManager = new BannerLayoutManager(getContext(), orientation);
|
||||||
|
mLayoutManager.setItemSpace(itemSpace);
|
||||||
|
mLayoutManager.setCenterScale(centerScale);
|
||||||
|
mLayoutManager.setMoveSpeed(moveSpeed);
|
||||||
|
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||||
|
new CenterSnapHelper().attachToRecyclerView(mRecyclerView);
|
||||||
|
|
||||||
|
|
||||||
|
//指示器部分
|
||||||
|
indicatorContainer = new RecyclerView(context);
|
||||||
|
LinearLayoutManager indicatorLayoutManager = new LinearLayoutManager(context, orientation, false);
|
||||||
|
indicatorContainer.setLayoutManager(indicatorLayoutManager);
|
||||||
|
indicatorAdapter = new IndicatorAdapter();
|
||||||
|
indicatorContainer.setAdapter(indicatorAdapter);
|
||||||
|
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.gravity = Gravity.BOTTOM | gravity;
|
||||||
|
params.setMargins(marginLeft, 0, marginRight, marginBottom);
|
||||||
|
addView(indicatorContainer, params);
|
||||||
|
if (!showIndicator) {
|
||||||
|
indicatorContainer.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置是否禁止滚动播放
|
||||||
|
public void setAutoPlaying(boolean isAutoPlaying) {
|
||||||
|
this.isAutoPlaying = isAutoPlaying;
|
||||||
|
setPlaying(this.isAutoPlaying);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置是否显示指示器
|
||||||
|
public void setShowIndicator(boolean showIndicator) {
|
||||||
|
this.showIndicator = showIndicator;
|
||||||
|
indicatorContainer.setVisibility(showIndicator ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置当前图片缩放系数
|
||||||
|
public void setCenterScale(float centerScale) {
|
||||||
|
this.centerScale = centerScale;
|
||||||
|
mLayoutManager.setCenterScale(centerScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置跟随手指的移动速度
|
||||||
|
public void setMoveSpeed(float moveSpeed) {
|
||||||
|
this.moveSpeed = moveSpeed;
|
||||||
|
mLayoutManager.setMoveSpeed(moveSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置图片间距
|
||||||
|
public void setItemSpace(int itemSpace) {
|
||||||
|
this.itemSpace = itemSpace;
|
||||||
|
mLayoutManager.setItemSpace(itemSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置轮播间隔时间
|
||||||
|
*
|
||||||
|
* @param autoPlayDuration 时间毫秒
|
||||||
|
*/
|
||||||
|
public void setAutoPlayDuration(int autoPlayDuration) {
|
||||||
|
this.autoPlayDuration = autoPlayDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrientation(int orientation) {
|
||||||
|
mLayoutManager.setOrientation(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否自动播放(上锁)
|
||||||
|
*
|
||||||
|
* @param playing 开始播放
|
||||||
|
*/
|
||||||
|
protected synchronized void setPlaying(boolean playing) {
|
||||||
|
if (isAutoPlaying && hasInit) {
|
||||||
|
if (!isPlaying && playing) {
|
||||||
|
mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
|
||||||
|
isPlaying = true;
|
||||||
|
} else if (isPlaying && !playing) {
|
||||||
|
mHandler.removeMessages(WHAT_AUTO_PLAY);
|
||||||
|
isPlaying = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置轮播数据集
|
||||||
|
*/
|
||||||
|
public void setAdapter(RecyclerView.Adapter adapter) {
|
||||||
|
hasInit = false;
|
||||||
|
mRecyclerView.setAdapter(adapter);
|
||||||
|
bannerSize = adapter.getItemCount();
|
||||||
|
mLayoutManager.setInfinite(bannerSize >= 3);
|
||||||
|
setPlaying(true);
|
||||||
|
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dx != 0) {
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
int first = mLayoutManager.getCurrentPosition();
|
||||||
|
Log.d("xxx", "onScrollStateChanged");
|
||||||
|
if (currentIndex != first) {
|
||||||
|
currentIndex = first;
|
||||||
|
}
|
||||||
|
if (newState == SCROLL_STATE_IDLE) {
|
||||||
|
setPlaying(true);
|
||||||
|
}
|
||||||
|
refreshIndicator();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hasInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
|
switch (ev.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
setPlaying(false);
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
setPlaying(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return super.dispatchTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
setPlaying(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onWindowVisibilityChanged(int visibility) {
|
||||||
|
super.onWindowVisibilityChanged(visibility);
|
||||||
|
if (visibility == VISIBLE) {
|
||||||
|
setPlaying(true);
|
||||||
|
} else {
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标示点适配器
|
||||||
|
*/
|
||||||
|
protected class IndicatorAdapter extends RecyclerView.Adapter {
|
||||||
|
|
||||||
|
int currentPosition = 0;
|
||||||
|
|
||||||
|
public void setPosition(int currentPosition) {
|
||||||
|
this.currentPosition = currentPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
|
||||||
|
ImageView bannerPoint = new ImageView(getContext());
|
||||||
|
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
lp.setMargins(indicatorMargin, indicatorMargin, indicatorMargin, indicatorMargin);
|
||||||
|
bannerPoint.setLayoutParams(lp);
|
||||||
|
return new RecyclerView.ViewHolder(bannerPoint) {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
|
ImageView bannerPoint = (ImageView) holder.itemView;
|
||||||
|
bannerPoint.setImageDrawable(currentPosition == position ? mSelectedDrawable : mUnselectedDrawable);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return bannerSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int dp2px(int dp) {
|
||||||
|
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
|
||||||
|
Resources.getSystem().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 改变导航的指示点
|
||||||
|
*/
|
||||||
|
protected synchronized void refreshIndicator() {
|
||||||
|
if (showIndicator && bannerSize > 1) {
|
||||||
|
indicatorAdapter.setPosition(currentIndex % bannerSize);
|
||||||
|
indicatorAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnBannerItemClickListener {
|
||||||
|
void onItemClick(int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
package com.example.library.banner;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.view.GravityCompat;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.PagerSnapHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.example.library.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class RecyclerViewBannerBase<L extends RecyclerView.LayoutManager, A extends RecyclerView.Adapter> extends FrameLayout {
|
||||||
|
|
||||||
|
protected int autoPlayDuration = 4000;//刷新间隔时间
|
||||||
|
|
||||||
|
protected boolean showIndicator;//是否显示指示器
|
||||||
|
protected RecyclerView indicatorContainer;
|
||||||
|
protected Drawable mSelectedDrawable;
|
||||||
|
protected Drawable mUnselectedDrawable;
|
||||||
|
protected IndicatorAdapter indicatorAdapter;
|
||||||
|
protected int indicatorMargin;//指示器间距
|
||||||
|
|
||||||
|
protected RecyclerView mRecyclerView;
|
||||||
|
protected A adapter;
|
||||||
|
protected L mLayoutManager;
|
||||||
|
|
||||||
|
protected int WHAT_AUTO_PLAY = 1000;
|
||||||
|
|
||||||
|
protected boolean hasInit;
|
||||||
|
protected int bannerSize = 1;
|
||||||
|
protected int currentIndex;
|
||||||
|
protected boolean isPlaying;
|
||||||
|
|
||||||
|
protected boolean isAutoPlaying;
|
||||||
|
protected List<String> tempUrlList = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
protected Handler mHandler = new Handler(new Handler.Callback() {
|
||||||
|
@Override
|
||||||
|
public boolean handleMessage(Message msg) {
|
||||||
|
if (msg.what == WHAT_AUTO_PLAY) {
|
||||||
|
mRecyclerView.smoothScrollToPosition(++currentIndex);
|
||||||
|
refreshIndicator();
|
||||||
|
mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public RecyclerViewBannerBase(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerViewBannerBase(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerViewBannerBase(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initView(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initView(Context context, AttributeSet attrs) {
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerViewBannerBase);
|
||||||
|
showIndicator = a.getBoolean(R.styleable.RecyclerViewBannerBase_showIndicator, true);
|
||||||
|
autoPlayDuration = a.getInt(R.styleable.RecyclerViewBannerBase_interval, 4000);
|
||||||
|
isAutoPlaying = a.getBoolean(R.styleable.RecyclerViewBannerBase_autoPlaying, true);
|
||||||
|
mSelectedDrawable = a.getDrawable(R.styleable.RecyclerViewBannerBase_indicatorSelectedSrc);
|
||||||
|
mUnselectedDrawable = a.getDrawable(R.styleable.RecyclerViewBannerBase_indicatorUnselectedSrc);
|
||||||
|
if (mSelectedDrawable == null) {
|
||||||
|
//绘制默认选中状态图形
|
||||||
|
GradientDrawable selectedGradientDrawable = new GradientDrawable();
|
||||||
|
selectedGradientDrawable.setShape(GradientDrawable.OVAL);
|
||||||
|
selectedGradientDrawable.setColor(Color.RED);
|
||||||
|
selectedGradientDrawable.setSize(dp2px(5), dp2px(5));
|
||||||
|
selectedGradientDrawable.setCornerRadius(dp2px(5) / 2);
|
||||||
|
mSelectedDrawable = new LayerDrawable(new Drawable[]{selectedGradientDrawable});
|
||||||
|
}
|
||||||
|
if (mUnselectedDrawable == null) {
|
||||||
|
//绘制默认未选中状态图形
|
||||||
|
GradientDrawable unSelectedGradientDrawable = new GradientDrawable();
|
||||||
|
unSelectedGradientDrawable.setShape(GradientDrawable.OVAL);
|
||||||
|
unSelectedGradientDrawable.setColor(Color.GRAY);
|
||||||
|
unSelectedGradientDrawable.setSize(dp2px(5), dp2px(5));
|
||||||
|
unSelectedGradientDrawable.setCornerRadius(dp2px(5) / 2);
|
||||||
|
mUnselectedDrawable = new LayerDrawable(new Drawable[]{unSelectedGradientDrawable});
|
||||||
|
}
|
||||||
|
|
||||||
|
indicatorMargin = a.getDimensionPixelSize(R.styleable.RecyclerViewBannerBase_indicatorSpace, dp2px(4));
|
||||||
|
int marginLeft = a.getDimensionPixelSize(R.styleable.RecyclerViewBannerBase_indicatorMarginLeft, dp2px(16));
|
||||||
|
int marginRight = a.getDimensionPixelSize(R.styleable.RecyclerViewBannerBase_indicatorMarginRight, dp2px(0));
|
||||||
|
int marginBottom = a.getDimensionPixelSize(R.styleable.RecyclerViewBannerBase_indicatorMarginBottom, dp2px(11));
|
||||||
|
int g = a.getInt(R.styleable.RecyclerViewBannerBase_indicatorGravity, 0);
|
||||||
|
int gravity;
|
||||||
|
if (g == 0) {
|
||||||
|
gravity = GravityCompat.START;
|
||||||
|
} else if (g == 2) {
|
||||||
|
gravity = GravityCompat.END;
|
||||||
|
} else {
|
||||||
|
gravity = Gravity.CENTER;
|
||||||
|
}
|
||||||
|
int o = a.getInt(R.styleable.RecyclerViewBannerBase_orientation, 0);
|
||||||
|
int orientation = 0;
|
||||||
|
if (o == 0) {
|
||||||
|
orientation = LinearLayoutManager.HORIZONTAL;
|
||||||
|
} else if (o == 1) {
|
||||||
|
orientation = LinearLayoutManager.VERTICAL;
|
||||||
|
}
|
||||||
|
a.recycle();
|
||||||
|
//recyclerView部分
|
||||||
|
mRecyclerView = new RecyclerView(context);
|
||||||
|
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
|
||||||
|
mLayoutManager = getLayoutManager(context, orientation);
|
||||||
|
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||||
|
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
onBannerScrolled(recyclerView, dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
onBannerScrollStateChanged(recyclerView, newState);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
LayoutParams vpLayoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
addView(mRecyclerView, vpLayoutParams);
|
||||||
|
//指示器部分
|
||||||
|
indicatorContainer = new RecyclerView(context);
|
||||||
|
|
||||||
|
LinearLayoutManager indicatorLayoutManager = new LinearLayoutManager(context, orientation, false);
|
||||||
|
indicatorContainer.setLayoutManager(indicatorLayoutManager);
|
||||||
|
indicatorAdapter = new IndicatorAdapter();
|
||||||
|
indicatorContainer.setAdapter(indicatorAdapter);
|
||||||
|
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.gravity = Gravity.BOTTOM | gravity;
|
||||||
|
params.setMargins(marginLeft, 0, marginRight, marginBottom);
|
||||||
|
addView(indicatorContainer, params);
|
||||||
|
if (!showIndicator) {
|
||||||
|
indicatorContainer.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onBannerScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onBannerScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract L getLayoutManager(Context context, int orientation);
|
||||||
|
|
||||||
|
protected abstract A getAdapter(Context context, List<String> list, OnBannerItemClickListener onBannerItemClickListener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置轮播间隔时间
|
||||||
|
*
|
||||||
|
* @param millisecond 时间毫秒
|
||||||
|
*/
|
||||||
|
public void setIndicatorInterval(int millisecond) {
|
||||||
|
this.autoPlayDuration = millisecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否自动播放(上锁)
|
||||||
|
*
|
||||||
|
* @param playing 开始播放
|
||||||
|
*/
|
||||||
|
protected synchronized void setPlaying(boolean playing) {
|
||||||
|
if (isAutoPlaying && hasInit) {
|
||||||
|
if (!isPlaying && playing ) {
|
||||||
|
mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
|
||||||
|
isPlaying = true;
|
||||||
|
} else if (isPlaying && !playing) {
|
||||||
|
mHandler.removeMessages(WHAT_AUTO_PLAY);
|
||||||
|
isPlaying = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否禁止滚动播放
|
||||||
|
*/
|
||||||
|
public void setAutoPlaying(boolean isAutoPlaying) {
|
||||||
|
this.isAutoPlaying = isAutoPlaying;
|
||||||
|
setPlaying(this.isAutoPlaying);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowIndicator(boolean showIndicator) {
|
||||||
|
this.showIndicator = showIndicator;
|
||||||
|
indicatorContainer.setVisibility(showIndicator ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置轮播数据集
|
||||||
|
*/
|
||||||
|
public void initBannerImageView(@NonNull List<String> newList, OnBannerItemClickListener onBannerItemClickListener) {
|
||||||
|
//解决recyclerView嵌套问题
|
||||||
|
if (compareListDifferent(newList, tempUrlList)) {
|
||||||
|
hasInit = false;
|
||||||
|
setVisibility(VISIBLE);
|
||||||
|
setPlaying(false);
|
||||||
|
adapter = getAdapter(getContext(), newList, onBannerItemClickListener);
|
||||||
|
mRecyclerView.setAdapter(adapter);
|
||||||
|
tempUrlList = newList;
|
||||||
|
bannerSize = tempUrlList.size();
|
||||||
|
if (bannerSize > 1) {
|
||||||
|
indicatorContainer.setVisibility(VISIBLE);
|
||||||
|
currentIndex = bannerSize * 10000;
|
||||||
|
mRecyclerView.scrollToPosition(currentIndex);
|
||||||
|
indicatorAdapter.notifyDataSetChanged();
|
||||||
|
setPlaying(true);
|
||||||
|
} else {
|
||||||
|
indicatorContainer.setVisibility(GONE);
|
||||||
|
currentIndex = 0;
|
||||||
|
}
|
||||||
|
hasInit = true;
|
||||||
|
}
|
||||||
|
if (!showIndicator) {
|
||||||
|
indicatorContainer.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置轮播数据集
|
||||||
|
*/
|
||||||
|
public void initBannerImageView(@NonNull List<String> newList) {
|
||||||
|
initBannerImageView(newList, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
|
switch (ev.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
setPlaying(false);
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
setPlaying(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//解决recyclerView嵌套问题
|
||||||
|
try {
|
||||||
|
return super.dispatchTouchEvent(ev);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//解决recyclerView嵌套问题
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
|
try {
|
||||||
|
return super.onTouchEvent(ev);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//解决recyclerView嵌套问题
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
try {
|
||||||
|
return super.onInterceptTouchEvent(ev);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
setPlaying(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onWindowVisibilityChanged(int visibility) {
|
||||||
|
super.onWindowVisibilityChanged(visibility);
|
||||||
|
if (visibility == VISIBLE) {
|
||||||
|
setPlaying(true);
|
||||||
|
} else {
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标示点适配器
|
||||||
|
*/
|
||||||
|
protected class IndicatorAdapter extends RecyclerView.Adapter {
|
||||||
|
|
||||||
|
int currentPosition = 0;
|
||||||
|
|
||||||
|
public void setPosition(int currentPosition) {
|
||||||
|
this.currentPosition = currentPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
|
||||||
|
ImageView bannerPoint = new ImageView(getContext());
|
||||||
|
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
lp.setMargins(indicatorMargin, indicatorMargin, indicatorMargin, indicatorMargin);
|
||||||
|
bannerPoint.setLayoutParams(lp);
|
||||||
|
return new RecyclerView.ViewHolder(bannerPoint) {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
|
ImageView bannerPoint = (ImageView) holder.itemView;
|
||||||
|
bannerPoint.setImageDrawable(currentPosition == position ? mSelectedDrawable : mUnselectedDrawable);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return bannerSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 改变导航的指示点
|
||||||
|
*/
|
||||||
|
protected synchronized void refreshIndicator() {
|
||||||
|
if (showIndicator && bannerSize > 1) {
|
||||||
|
indicatorAdapter.setPosition(currentIndex % bannerSize);
|
||||||
|
indicatorAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnBannerItemClickListener {
|
||||||
|
void onItemClick(int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int dp2px(int dp) {
|
||||||
|
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
|
||||||
|
Resources.getSystem().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取颜色
|
||||||
|
*/
|
||||||
|
protected int getColor(@ColorRes int color) {
|
||||||
|
return ContextCompat.getColor(getContext(), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean compareListDifferent(List<String> newTabList, List<String> oldTabList) {
|
||||||
|
if (oldTabList == null || oldTabList.isEmpty())
|
||||||
|
return true;
|
||||||
|
if (newTabList.size() != oldTabList.size())
|
||||||
|
return true;
|
||||||
|
for (int i = 0; i < newTabList.size(); i++) {
|
||||||
|
if (TextUtils.isEmpty(newTabList.get(i)))
|
||||||
|
return true;
|
||||||
|
if (!newTabList.get(i).equals(oldTabList.get(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,962 @@
|
|||||||
|
package com.example.library.banner.layoutmanager;
|
||||||
|
|
||||||
|
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.core.view.ViewCompat;
|
||||||
|
import androidx.recyclerview.widget.OrientationHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 class BannerLayoutManager extends RecyclerView.LayoutManager {
|
||||||
|
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
|
||||||
|
* 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 = true;
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @return the mInterval of each item's mOffset
|
||||||
|
*/
|
||||||
|
private int itemSpace = 20;
|
||||||
|
|
||||||
|
private float centerScale = 1.2f;
|
||||||
|
private float moveSpeed=1.0f;
|
||||||
|
|
||||||
|
|
||||||
|
protected float getDistanceRatio() {
|
||||||
|
if (moveSpeed == 0) return Float.MAX_VALUE;
|
||||||
|
return 1 / moveSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected float setInterval() {
|
||||||
|
return mDecoratedMeasurement * ((centerScale - 1) / 2 + 1) + itemSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemSpace(int itemSpace) {
|
||||||
|
this.itemSpace = itemSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCenterScale(float centerScale) {
|
||||||
|
this.centerScale = centerScale;
|
||||||
|
}
|
||||||
|
public void setMoveSpeed(float moveSpeed) {
|
||||||
|
assertNotInLayoutOrScroll(null);
|
||||||
|
if (this.moveSpeed == moveSpeed) return;
|
||||||
|
this.moveSpeed = moveSpeed;
|
||||||
|
}
|
||||||
|
protected void setItemViewProperty(View itemView, float targetOffset) {
|
||||||
|
float scale = calculateScale(targetOffset + mSpaceMain);
|
||||||
|
itemView.setScaleX(scale);
|
||||||
|
itemView.setScaleY(scale);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @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 - (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2f);
|
||||||
|
float diff = 0f;
|
||||||
|
if ((mDecoratedMeasurement - deltaX) > 0) diff = mDecoratedMeasurement - deltaX;
|
||||||
|
return (centerScale - 1f) / mDecoratedMeasurement * diff + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 BannerLayoutManager(Context context) {
|
||||||
|
this(context, HORIZONTAL, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
|
||||||
|
*/
|
||||||
|
public BannerLayoutManager(Context context, int orientation) {
|
||||||
|
this(context,orientation,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BannerLayoutManager(Context context, int orientation, boolean reverseLayout) {
|
||||||
|
setEnableBringCenterToFront(true);
|
||||||
|
setMaxVisibleItemCount(3);
|
||||||
|
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 BannerLayoutManager}
|
||||||
|
* 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() {
|
||||||
|
if (mOrientation == HORIZONTAL && getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
|
||||||
|
mReverseLayout = !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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 = getOffsetToPosition(position);
|
||||||
|
if (mOrientation == VERTICAL) {
|
||||||
|
recyclerView.smoothScrollBy(0, offsetPosition, mSmoothScrollInterpolator);
|
||||||
|
} else {
|
||||||
|
recyclerView.smoothScrollBy(offsetPosition, 0, mSmoothScrollInterpolator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void scrollToPosition(int position) {
|
||||||
|
if (!mInfinite && (position < 0 || position >= getItemCount())) return;
|
||||||
|
mPendingScrollPosition = position;
|
||||||
|
mOffset = mShouldReverseLayout ? position * -mInterval : position * mInterval;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = recycler.getViewForPosition(0);
|
||||||
|
measureChildWithMargins(scrap, 0, 0);
|
||||||
|
mDecoratedMeasurement = mOrientationHelper.getDecoratedMeasurement(scrap);
|
||||||
|
mDecoratedMeasurementInOther = mOrientationHelper.getDecoratedMeasurementInOther(scrap);
|
||||||
|
mSpaceMain = (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2;
|
||||||
|
if (mDistanceToBottom == INVALID_SIZE) {
|
||||||
|
mSpaceInOther = (getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2;
|
||||||
|
} else {
|
||||||
|
mSpaceInOther =getTotalSpaceInOther() - mDecoratedMeasurementInOther - mDistanceToBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
mInterval = setInterval();
|
||||||
|
setUp();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
detachAndScrapAttachedViews(recycler);
|
||||||
|
layoutItems(recycler);
|
||||||
|
}
|
||||||
|
public int getTotalSpaceInOther() {
|
||||||
|
if (mOrientation == HORIZONTAL) {
|
||||||
|
return getHeight() - getPaddingTop()
|
||||||
|
- getPaddingBottom();
|
||||||
|
} else {
|
||||||
|
return getWidth() - getPaddingLeft()
|
||||||
|
- getPaddingRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@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;
|
||||||
|
recyclerView.smoothScrollToPosition(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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCurrentPositionOffset() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @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 - getCurrentPosition() : getCurrentPosition() - 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 ?
|
||||||
|
(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 Parcelable.Creator<SavedState> CREATOR
|
||||||
|
= new Parcelable.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.example.library.banner.layoutmanager;
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to center the current position
|
||||||
|
*/
|
||||||
|
public class CenterScrollListener extends RecyclerView.OnScrollListener {
|
||||||
|
private boolean mAutoSet = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||||
|
|
||||||
|
final OverFlyingLayoutManager.OnPageChangeListener onPageChangeListener = ((OverFlyingLayoutManager) layoutManager).onPageChangeListener;
|
||||||
|
if (onPageChangeListener != null) {
|
||||||
|
onPageChangeListener.onPageScrollStateChanged(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||||
|
if (mAutoSet) {
|
||||||
|
if (onPageChangeListener != null) {
|
||||||
|
onPageChangeListener.onPageSelected(((OverFlyingLayoutManager) layoutManager).getCurrentPosition());
|
||||||
|
}
|
||||||
|
mAutoSet = false;
|
||||||
|
} else {
|
||||||
|
final int delta;
|
||||||
|
delta = ((OverFlyingLayoutManager) layoutManager).getOffsetToCenter();
|
||||||
|
if (delta != 0) {
|
||||||
|
if (((OverFlyingLayoutManager) layoutManager).getOrientation() == OverFlyingLayoutManager.VERTICAL)
|
||||||
|
recyclerView.smoothScrollBy(0, delta);
|
||||||
|
else
|
||||||
|
recyclerView.smoothScrollBy(delta, 0);
|
||||||
|
mAutoSet = true;
|
||||||
|
} else {
|
||||||
|
if (onPageChangeListener != null) {
|
||||||
|
onPageChangeListener.onPageSelected(((OverFlyingLayoutManager) layoutManager).getCurrentPosition());
|
||||||
|
}
|
||||||
|
mAutoSet = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) {
|
||||||
|
mAutoSet = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package com.example.library.banner.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}
|
||||||
|
* <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(BannerLayoutManager, BannerLayoutManager.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 BannerLayoutManager layoutManager =
|
||||||
|
(BannerLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
final BannerLayoutManager.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) {
|
||||||
|
BannerLayoutManager layoutManager = (BannerLayoutManager) 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 == BannerLayoutManager.VERTICAL
|
||||||
|
&& Math.abs(velocityY) > minFlingVelocity) {
|
||||||
|
final int currentPosition = layoutManager.getCurrentPosition();
|
||||||
|
final int offsetPosition = (int) (mGravityScroller.getFinalY() /
|
||||||
|
layoutManager.mInterval / layoutManager.getDistanceRatio());
|
||||||
|
mRecyclerView.smoothScrollToPosition(layoutManager.getReverseLayout() ?
|
||||||
|
currentPosition - offsetPosition : currentPosition + offsetPosition);
|
||||||
|
return true;
|
||||||
|
} else if (layoutManager.mOrientation == BannerLayoutManager.HORIZONTAL
|
||||||
|
&& Math.abs(velocityX) > minFlingVelocity) {
|
||||||
|
final int currentPosition = layoutManager.getCurrentPosition();
|
||||||
|
final int offsetPosition = (int) (mGravityScroller.getFinalX() /
|
||||||
|
layoutManager.mInterval / layoutManager.getDistanceRatio());
|
||||||
|
mRecyclerView.smoothScrollToPosition(layoutManager.getReverseLayout() ?
|
||||||
|
currentPosition - offsetPosition : currentPosition + offsetPosition);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 BannerLayoutManager)) return;
|
||||||
|
|
||||||
|
setupCallbacks();
|
||||||
|
mGravityScroller = new Scroller(mRecyclerView.getContext(),
|
||||||
|
new DecelerateInterpolator());
|
||||||
|
|
||||||
|
snapToCenterView((BannerLayoutManager) layoutManager,
|
||||||
|
((BannerLayoutManager) layoutManager).onPageChangeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void snapToCenterView(BannerLayoutManager layoutManager,
|
||||||
|
BannerLayoutManager.OnPageChangeListener listener) {
|
||||||
|
final int delta = layoutManager.getOffsetToCenter();
|
||||||
|
if (delta != 0) {
|
||||||
|
if (layoutManager.getOrientation()
|
||||||
|
== BannerLayoutManager.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,899 @@
|
|||||||
|
package com.example.library.banner.layoutmanager;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||||
|
import androidx.recyclerview.widget.OrientationHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please make sure your child view have the same size.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"})
|
||||||
|
public class OverFlyingLayoutManager extends RecyclerView.LayoutManager
|
||||||
|
implements RecyclerView.SmoothScroller.ScrollVectorProvider {
|
||||||
|
private float minScale = 0.75f;//两侧图片缩放比
|
||||||
|
private float angle = 8f;//翻转角度
|
||||||
|
private int itemSpace = 385;
|
||||||
|
private boolean mInfinite = true;
|
||||||
|
public static final int DETERMINE_BY_MAX_AND_MIN = -1;
|
||||||
|
|
||||||
|
public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
|
||||||
|
|
||||||
|
public static final int VERTICAL = OrientationHelper.VERTICAL;
|
||||||
|
|
||||||
|
protected int mDecoratedMeasurement;
|
||||||
|
|
||||||
|
protected int mDecoratedMeasurementInOther;
|
||||||
|
|
||||||
|
public float getMinScale() {
|
||||||
|
return minScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinScale(float minScale) {
|
||||||
|
this.minScale = minScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAngle() {
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAngle(float angle) {
|
||||||
|
this.angle = angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemSpace() {
|
||||||
|
return itemSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemSpace(int itemSpace) {
|
||||||
|
this.itemSpace = itemSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = RecyclerView.NO_POSITION;
|
||||||
|
|
||||||
|
private SavedState mPendingSavedState = null;
|
||||||
|
|
||||||
|
protected float mInterval; //the mInterval of each item's mOffset
|
||||||
|
|
||||||
|
/* package */ OnPageChangeListener onPageChangeListener;
|
||||||
|
|
||||||
|
private boolean mRecycleChildrenOnDetach;
|
||||||
|
|
||||||
|
|
||||||
|
private boolean mEnableBringCenterToFront;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ugly code for fix bug caused by float
|
||||||
|
*/
|
||||||
|
private boolean mIntegerDy = false;
|
||||||
|
|
||||||
|
private int mLeftItems;
|
||||||
|
|
||||||
|
private int mRightItems;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* max visible item count
|
||||||
|
*/
|
||||||
|
private int mMaxVisibleItemCount = DETERMINE_BY_MAX_AND_MIN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the mInterval of each item's mOffset
|
||||||
|
*/
|
||||||
|
protected float setInterval() {
|
||||||
|
return mDecoratedMeasurement - itemSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setItemViewProperty(View itemView, float targetOffset) {
|
||||||
|
float scale = calculateScale(targetOffset + mSpaceMain);
|
||||||
|
itemView.setScaleX(scale);
|
||||||
|
itemView.setScaleY(scale);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
itemView.setElevation(0);
|
||||||
|
}
|
||||||
|
final float rotation = calRotation(targetOffset);
|
||||||
|
if (getOrientation() == HORIZONTAL) {
|
||||||
|
itemView.setRotationY(rotation);
|
||||||
|
} else {
|
||||||
|
itemView.setRotationX(-rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calRotation(float targetOffset) {
|
||||||
|
return -angle / mInterval * targetOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calculateScale(float x) {
|
||||||
|
float deltaX = Math.abs(x - (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2f);
|
||||||
|
return (minScale - 1) * deltaX / (mOrientationHelper.getTotalSpace() / 2f) + 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 itemView.getScaleX() * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a horizontal ViewPagerLayoutManager
|
||||||
|
*/
|
||||||
|
public OverFlyingLayoutManager(Context context) {
|
||||||
|
this(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 OverFlyingLayoutManager(int orientation, boolean reverseLayout) {
|
||||||
|
setOrientation(orientation);
|
||||||
|
setReverseLayout(reverseLayout);
|
||||||
|
setAutoMeasureEnabled(true);
|
||||||
|
setEnableBringCenterToFront(true);
|
||||||
|
setIntegerDy(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OverFlyingLayoutManager(float minScale, int itemSpace, int orientation) {
|
||||||
|
this(orientation, false);
|
||||||
|
this.minScale = minScale;
|
||||||
|
this.itemSpace = itemSpace;
|
||||||
|
mOrientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = mReverseLayout;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* see {@link #mIntegerDy}
|
||||||
|
*/
|
||||||
|
public boolean isIntegerDy() {
|
||||||
|
return mIntegerDy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* see {@link #mIntegerDy}
|
||||||
|
*/
|
||||||
|
public void setIntegerDy(boolean mIntegerDy) {
|
||||||
|
this.mIntegerDy = mIntegerDy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
if (mOrientation == HORIZONTAL && getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
|
||||||
|
mReverseLayout = !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.
|
||||||
|
* from LTR.
|
||||||
|
*/
|
||||||
|
public void setReverseLayout(boolean reverseLayout) {
|
||||||
|
assertNotInLayoutOrScroll(null);
|
||||||
|
if (reverseLayout == mReverseLayout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mReverseLayout = reverseLayout;
|
||||||
|
removeAllViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||||
|
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext());
|
||||||
|
linearSmoothScroller.setTargetPosition(position);
|
||||||
|
startSmoothScroll(linearSmoothScroller);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||||
|
if (getChildCount() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final int firstChildPos = getPosition(getChildAt(0));
|
||||||
|
final float direction = targetPosition < firstChildPos == !mReverseLayout ?
|
||||||
|
-1 / getDistanceRatio() : 1 / getDistanceRatio();
|
||||||
|
if (mOrientation == HORIZONTAL) {
|
||||||
|
return new PointF(direction, 0);
|
||||||
|
} else {
|
||||||
|
return new PointF(0, direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = recycler.getViewForPosition(0);
|
||||||
|
measureChildWithMargins(scrap, 0, 0);
|
||||||
|
mDecoratedMeasurement = mOrientationHelper.getDecoratedMeasurement(scrap);
|
||||||
|
mDecoratedMeasurementInOther = mOrientationHelper.getDecoratedMeasurementInOther(scrap);
|
||||||
|
mSpaceMain = (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2;
|
||||||
|
mSpaceInOther = (getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2;
|
||||||
|
mInterval = setInterval();
|
||||||
|
setUp();
|
||||||
|
mLeftItems = (int) Math.abs(minRemoveOffset() / mInterval) + 1;
|
||||||
|
mRightItems = (int) Math.abs(maxRemoveOffset() / mInterval) + 1;
|
||||||
|
|
||||||
|
if (mPendingSavedState != null) {
|
||||||
|
mReverseLayout = mPendingSavedState.isReverseLayout;
|
||||||
|
mPendingScrollPosition = mPendingSavedState.position;
|
||||||
|
mOffset = mPendingSavedState.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
|
||||||
|
mOffset = mReverseLayout ?
|
||||||
|
mPendingScrollPosition * -mInterval : mPendingScrollPosition * mInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
detachAndScrapAttachedViews(recycler);
|
||||||
|
layoutItems(recycler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalSpaceInOther() {
|
||||||
|
if (mOrientation == HORIZONTAL) {
|
||||||
|
return getHeight() - getPaddingTop()
|
||||||
|
- getPaddingBottom();
|
||||||
|
} else {
|
||||||
|
return getWidth() - getPaddingLeft()
|
||||||
|
- getPaddingRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLayoutCompleted(RecyclerView.State state) {
|
||||||
|
super.onLayoutCompleted(state);
|
||||||
|
mPendingSavedState = null;
|
||||||
|
mPendingScrollPosition = RecyclerView.NO_POSITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mReverseLayout ? position * -mInterval : position * mInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
|
||||||
|
removeAllViews();
|
||||||
|
mOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scrollToPosition(int position) {
|
||||||
|
mPendingScrollPosition = position;
|
||||||
|
mOffset = mReverseLayout ? 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 !mReverseLayout ?
|
||||||
|
getCurrentPosition() : getItemCount() - getCurrentPosition() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float realOffset = getOffsetOfRightAdapterPosition();
|
||||||
|
return !mReverseLayout ? (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());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIntegerDy) {
|
||||||
|
realDx = (int) (willScroll / getDistanceRatio());
|
||||||
|
} else {
|
||||||
|
realDx = willScroll / getDistanceRatio();
|
||||||
|
}
|
||||||
|
|
||||||
|
mOffset += realDx;
|
||||||
|
|
||||||
|
// we re-layout all current views in the right place
|
||||||
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
|
final View scrap = getChildAt(i);
|
||||||
|
final float delta = propertyChangeWhenScroll(scrap) - realDx;
|
||||||
|
layoutScrap(scrap, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
//handle recycle
|
||||||
|
layoutItems(recycler);
|
||||||
|
|
||||||
|
return willScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void layoutItems(RecyclerView.Recycler recycler) {
|
||||||
|
detachAndScrapAttachedViews(recycler);
|
||||||
|
|
||||||
|
// make sure that current position start from 0 to 1
|
||||||
|
final int currentPos = mReverseLayout ?
|
||||||
|
-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int itemCount = getItemCount();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
lastOrderWeight = orderWeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getMaxOffset() {
|
||||||
|
return !mReverseLayout ? (getItemCount() - 1) * mInterval : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getMinOffset() {
|
||||||
|
return !mReverseLayout ? 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 propertyChangeWhenScroll(View itemView) {
|
||||||
|
if (mOrientation == VERTICAL)
|
||||||
|
return itemView.getTop() - mSpaceMain;
|
||||||
|
return itemView.getLeft() - mSpaceMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getDistanceRatio() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentPosition() {
|
||||||
|
int position = getCurrentPositionOffset();
|
||||||
|
if (!mInfinite) return Math.abs(position);
|
||||||
|
position = !mReverseLayout ?
|
||||||
|
//take care of position = getItemCount()
|
||||||
|
(position >= 0 ?
|
||||||
|
position % getItemCount() :
|
||||||
|
getItemCount() + position % getItemCount()) :
|
||||||
|
(position > 0 ?
|
||||||
|
getItemCount() - position % getItemCount() :
|
||||||
|
-position % getItemCount());
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCurrentPositionOffset() {
|
||||||
|
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 (mReverseLayout)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the dy between center and current position
|
||||||
|
*/
|
||||||
|
public int getOffsetToCenter() {
|
||||||
|
if (mInfinite)
|
||||||
|
return (int) ((getCurrentPositionOffset() * mInterval - mOffset) * getDistanceRatio());
|
||||||
|
return (int) ((getCurrentPosition() *
|
||||||
|
(!mReverseLayout ? 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
RecyclerBanner/src/main/res/values/attr.xml
Normal file
39
RecyclerBanner/src/main/res/values/attr.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<attr name="interval" format="integer"/>
|
||||||
|
<attr name="showIndicator" format="boolean"/>
|
||||||
|
<attr name="orientation" format="enum">
|
||||||
|
<enum name="horizontal" value="0"/>
|
||||||
|
<enum name="vertical" value="1"/>
|
||||||
|
</attr>
|
||||||
|
<attr name="autoPlaying" format="boolean"/>
|
||||||
|
<declare-styleable name="RecyclerViewBannerBase">
|
||||||
|
<attr name="interval"/>
|
||||||
|
<attr name="showIndicator" />
|
||||||
|
<attr name="orientation" />
|
||||||
|
<attr name="autoPlaying"/>
|
||||||
|
<attr name="indicatorSelectedSrc" format="color|reference"/>
|
||||||
|
<attr name="indicatorUnselectedSrc" format="color|reference"/>
|
||||||
|
<attr name="indicatorSpace" format="dimension|reference"/>
|
||||||
|
<attr name="indicatorMarginLeft" format="dimension|reference"/>
|
||||||
|
<attr name="indicatorMarginRight" format="dimension|reference"/>
|
||||||
|
<attr name="indicatorMarginBottom" format="dimension|reference"/>
|
||||||
|
<attr name="indicatorGravity" format="enum">
|
||||||
|
<enum name="left" value="0"/>
|
||||||
|
<enum name="center" value="1"/>
|
||||||
|
<enum name="right" value="2"/>
|
||||||
|
</attr>
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="BannerLayout">
|
||||||
|
<attr name="interval" />
|
||||||
|
<attr name="showIndicator" />
|
||||||
|
<attr name="orientation" />
|
||||||
|
<attr name="autoPlaying"/>
|
||||||
|
<attr name="itemSpace" format="integer"/>
|
||||||
|
<attr name="centerScale" format="float"/>
|
||||||
|
<attr name="moveSpeed" format="float"/>
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
|
||||||
|
</resources>
|
||||||
3
RecyclerBanner/src/main/res/values/strings.xml
Normal file
3
RecyclerBanner/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">library</string>
|
||||||
|
</resources>
|
||||||
13
app/app.iml
13
app/app.iml
File diff suppressed because one or more lines are too long
@@ -17,8 +17,8 @@ android {
|
|||||||
applicationId "com.appstore.uiui"
|
applicationId "com.appstore.uiui"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 16
|
versionCode 18
|
||||||
versionName "1.1.6"
|
versionName "1.1.8"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
//极光
|
//极光
|
||||||
ndk {
|
ndk {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -93,13 +93,13 @@ public class MyApplication extends Application {
|
|||||||
}
|
}
|
||||||
//全局字体初始化
|
//全局字体初始化
|
||||||
|
|
||||||
CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
|
// CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
|
||||||
// .setDefaultFontPath("fonts/1234.ttf")
|
// // .setDefaultFontPath("fonts/1234.ttf")
|
||||||
// .setDefaultFontPath("fonts/SourceHanSansCN-ExtraLight.ttf")
|
// .setDefaultFontPath("fonts/SourceHanSansCN-ExtraLight.ttf")
|
||||||
.setDefaultFontPath("fonts/test.ttf")
|
//// .setDefaultFontPath("fonts/test.ttf")
|
||||||
.setFontAttrId(R.attr.fontPath)
|
// .setFontAttrId(R.attr.fontPath)
|
||||||
.build()
|
// .build()
|
||||||
);
|
// );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
<item name="fontPath">fonts/test.ttf</item>
|
<!-- <item name="fontPath">fonts/test.ttf</item>-->
|
||||||
<!-- <item name="fontPath">fonts/SourceHanSansCN-ExtraLight.ttf</item>-->
|
<!-- <item name="fontPath">fonts/SourceHanSansCN-ExtraLight.ttf</item>-->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user