卡片交互效果
@@ -20,12 +20,12 @@ android {
|
||||
}
|
||||
|
||||
multiDexEnabled true
|
||||
externalNativeBuild {
|
||||
ndk {
|
||||
// 设置支持的SO库架构
|
||||
abiFilters 'armeabi-v7a'
|
||||
}
|
||||
}
|
||||
// externalNativeBuild {
|
||||
// ndk {
|
||||
// // 设置支持的SO库架构
|
||||
// abiFilters 'armeabi-v7a'
|
||||
// }
|
||||
// }
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
|
||||
@@ -21,6 +21,8 @@ buildscript {
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.alibaba:arouter-register:1.0.2"
|
||||
classpath 'com.novoda:bintray-release:0.8.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ ext {
|
||||
modulecommon : "com.mogo.module:module-common:${MOGO_MODULE_COMMON_VERSION}",
|
||||
modulemain : "com.mogo.module:module-main:${MOGO_MODULE_MAIN_VERSION}",
|
||||
modulemap : "com.mogo.module:module-map:${MOGO_MODULE_MAP_VERSION}",
|
||||
moduleservice : "com.mogo.module:module-service:${MOGO_MODULE_SERVICE_VERSION}",
|
||||
moduleservice : "com.mogo.module:module-service:${CARD_LIBRARY_VERSION}",
|
||||
uicard : "com.mogo.ui:card-libaray:${MOGO_MODULE_SERVICE_VERSION}",
|
||||
mogoservice : "com.mogo.service:mogo-service:${MOGO_SERVICE_VERSION}",
|
||||
mogoserviceapi : "com.mogo.service:mogo-service-api:${MOGO_SERVICE_API_VERSION}",
|
||||
moduleapps : "com.mogo.module:module-apps:${MOGO_MODULE_APPS_VERSION}",
|
||||
|
||||
@@ -66,3 +66,5 @@ MOGO_MODULE_AD_CARD_VERSION=1.0.0-SNAPSHOT
|
||||
# 新鲜水
|
||||
MOGO_MODULE_FRESH_NEWS_VERSION=1.0.0-SNAPSHOT
|
||||
|
||||
# 卡片效果
|
||||
CARD_LIBRARY_VERSION=1.0.0-SNAPSHOT
|
||||
1
libraries/card-library/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
36
libraries/card-library/build.gradle
Normal file
@@ -0,0 +1,36 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'com.novoda.bintray-release'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
versionCode Integer.valueOf(VERSION_CODE)
|
||||
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation rootProject.ext.dependencies.androidxappcompat
|
||||
implementation rootProject.ext.dependencies.androidxrecyclerview
|
||||
testImplementation 'org.robolectric:robolectric:3.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.13.0'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:1.3'
|
||||
androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
|
||||
}
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
|
||||
//publish {
|
||||
// artifactId = 'discrete-scrollview'
|
||||
// userOrg = rootProject.userOrg
|
||||
// groupId = rootProject.groupId
|
||||
// uploadName = rootProject.uploadName
|
||||
// publishVersion = rootProject.publishVersion
|
||||
// description = rootProject.description
|
||||
// licences = rootProject.licences
|
||||
//}
|
||||
3
libraries/card-library/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
GROUP=com.mogo.ui
|
||||
POM_ARTIFACT_ID=card-libaray
|
||||
VERSION_CODE=1
|
||||
17
libraries/card-library/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in C:\Users\yarolegovich\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
1
libraries/card-library/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="com.yarolegovich.discretescrollview" />
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 16.03.2017.
|
||||
*/
|
||||
public enum DSVOrientation {
|
||||
|
||||
HORIZONTAL {
|
||||
@Override
|
||||
Helper createHelper() {
|
||||
return new HorizontalHelper();
|
||||
}
|
||||
},
|
||||
VERTICAL {
|
||||
@Override
|
||||
Helper createHelper() {
|
||||
return new VerticalHelper();
|
||||
}
|
||||
};
|
||||
|
||||
//Package private
|
||||
abstract Helper createHelper();
|
||||
|
||||
interface Helper {
|
||||
|
||||
int getViewEnd(int recyclerWidth, int recyclerHeight);
|
||||
|
||||
int getDistanceToChangeCurrent(int childWidth, int childHeight);
|
||||
|
||||
void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint);
|
||||
|
||||
void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter);
|
||||
|
||||
int getFlingVelocity(int velocityX, int velocityY);
|
||||
|
||||
int getPendingDx(int pendingScroll);
|
||||
|
||||
int getPendingDy(int pendingScroll);
|
||||
|
||||
void offsetChildren(int amount, RecyclerViewProxy lm);
|
||||
|
||||
float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY);
|
||||
|
||||
boolean isViewVisible(Point center, int halfWidth, int halfHeight, int endBound, int extraSpace);
|
||||
|
||||
boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm);
|
||||
|
||||
boolean canScrollVertically();
|
||||
|
||||
boolean canScrollHorizontally();
|
||||
}
|
||||
|
||||
protected static class HorizontalHelper implements Helper {
|
||||
|
||||
@Override
|
||||
public int getViewEnd(int recyclerWidth, int recyclerHeight) {
|
||||
return recyclerWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDistanceToChangeCurrent(int childWidth, int childHeight) {
|
||||
return childWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint) {
|
||||
int newX = recyclerCenter.x - scrolled;
|
||||
outPoint.set(newX, recyclerCenter.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter) {
|
||||
int newX = outCenter.x + direction.applyTo(shiftAmount);
|
||||
outCenter.set(newX, outCenter.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewVisible(
|
||||
Point viewCenter, int halfWidth, int halfHeight, int endBound,
|
||||
int extraSpace) {
|
||||
int viewLeft = viewCenter.x - halfWidth;
|
||||
int viewRight = viewCenter.x + halfWidth;
|
||||
return viewLeft < (endBound + extraSpace) && viewRight > -extraSpace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm) {
|
||||
View firstChild = lm.getFirstChild(), lastChild = lm.getLastChild();
|
||||
int leftBound = -lm.getExtraLayoutSpace();
|
||||
int rightBound = lm.getWidth() + lm.getExtraLayoutSpace();
|
||||
boolean isNewVisibleFromLeft = lm.getDecoratedLeft(firstChild) > leftBound
|
||||
&& lm.getPosition(firstChild) > 0;
|
||||
boolean isNewVisibleFromRight = lm.getDecoratedRight(lastChild) < rightBound
|
||||
&& lm.getPosition(lastChild) < lm.getItemCount() - 1;
|
||||
return isNewVisibleFromLeft || isNewVisibleFromRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount, RecyclerViewProxy helper) {
|
||||
helper.offsetChildrenHorizontal(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY) {
|
||||
return viewCenterX - center.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlingVelocity(int velocityX, int velocityY) {
|
||||
return velocityX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDx(int pendingScroll) {
|
||||
return pendingScroll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDy(int pendingScroll) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static class VerticalHelper implements Helper {
|
||||
|
||||
@Override
|
||||
public int getViewEnd(int recyclerWidth, int recyclerHeight) {
|
||||
return recyclerHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDistanceToChangeCurrent(int childWidth, int childHeight) {
|
||||
return childHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint) {
|
||||
int newY = recyclerCenter.y - scrolled;
|
||||
outPoint.set(recyclerCenter.x, newY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter) {
|
||||
int newY = outCenter.y + direction.applyTo(shiftAmount);
|
||||
outCenter.set(outCenter.x, newY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount, RecyclerViewProxy helper) {
|
||||
helper.offsetChildrenVertical(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY) {
|
||||
return viewCenterY - center.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewVisible(
|
||||
Point viewCenter, int halfWidth, int halfHeight, int endBound,
|
||||
int extraSpace) {
|
||||
int viewTop = viewCenter.y - halfHeight;
|
||||
int viewBottom = viewCenter.y + halfHeight;
|
||||
return viewTop < (endBound + extraSpace) && viewBottom > -extraSpace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm) {
|
||||
View firstChild = lm.getFirstChild(), lastChild = lm.getLastChild();
|
||||
int topBound = -lm.getExtraLayoutSpace();
|
||||
int bottomBound = lm.getHeight() + lm.getExtraLayoutSpace();
|
||||
boolean isNewVisibleFromTop = lm.getDecoratedTop(firstChild) > topBound
|
||||
&& lm.getPosition(firstChild) > 0;
|
||||
boolean isNewVisibleFromBottom = lm.getDecoratedBottom(lastChild) < bottomBound
|
||||
&& lm.getPosition(lastChild) < lm.getItemCount() - 1;
|
||||
return isNewVisibleFromTop || isNewVisibleFromBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlingVelocity(int velocityX, int velocityY) {
|
||||
return velocityY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDx(int pendingScroll) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDy(int pendingScroll) {
|
||||
return pendingScroll;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 16.03.2017.
|
||||
*/
|
||||
enum Direction {
|
||||
|
||||
START {
|
||||
@Override
|
||||
public int applyTo(int delta) {
|
||||
return delta * -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(int direction) {
|
||||
return direction < 0;
|
||||
}
|
||||
},
|
||||
END {
|
||||
@Override
|
||||
public int applyTo(int delta) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(int direction) {
|
||||
return direction > 0;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract int applyTo(int delta);
|
||||
|
||||
public abstract boolean sameAs(int direction);
|
||||
|
||||
public static Direction fromDelta(int delta) {
|
||||
return delta > 0 ? END : START;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.accessibility.AccessibilityEventCompat;
|
||||
import androidx.core.view.accessibility.AccessibilityRecordCompat;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.yarolegovich.discretescrollview.transform.DiscreteScrollItemTransformer;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 17.02.2017.
|
||||
*/
|
||||
class DiscreteScrollLayoutManager extends RecyclerView.LayoutManager {
|
||||
|
||||
static final int NO_POSITION = -1;
|
||||
|
||||
private static final String EXTRA_POSITION = "extra_position";
|
||||
private static final int DEFAULT_TIME_FOR_ITEM_SETTLE = 300;
|
||||
private static final int DEFAULT_FLING_THRESHOLD = 2100; //Decrease to increase sensitivity.
|
||||
private static final int DEFAULT_TRANSFORM_CLAMP_ITEM_COUNT = 1;
|
||||
|
||||
protected static final float SCROLL_TO_SNAP_TO_ANOTHER_ITEM = 0.6f;
|
||||
|
||||
//This field will take value of all visible view's center points during the fill phase
|
||||
protected Point viewCenterIterator;
|
||||
protected Point recyclerCenter;
|
||||
protected Point currentViewCenter;
|
||||
protected int childHalfWidth, childHalfHeight;
|
||||
protected int extraLayoutSpace;
|
||||
|
||||
//Max possible distance a view can travel during one scroll phase
|
||||
protected int scrollToChangeCurrent;
|
||||
protected int currentScrollState;
|
||||
|
||||
protected int scrolled;
|
||||
protected int pendingScroll;
|
||||
protected int currentPosition;
|
||||
protected int pendingPosition;
|
||||
|
||||
protected SparseArray<View> detachedCache;
|
||||
|
||||
private DSVOrientation.Helper orientationHelper;
|
||||
|
||||
protected boolean isFirstOrEmptyLayout;
|
||||
|
||||
private Context context;
|
||||
|
||||
private int timeForItemSettle;
|
||||
private int offscreenItems;
|
||||
private int transformClampItemCount;
|
||||
|
||||
private boolean dataSetChangeShiftedPosition;
|
||||
|
||||
private int flingThreshold;
|
||||
private boolean shouldSlideOnFling;
|
||||
|
||||
private int viewWidth, viewHeight;
|
||||
|
||||
@NonNull
|
||||
private final ScrollStateListener scrollStateListener;
|
||||
private DiscreteScrollItemTransformer itemTransformer;
|
||||
|
||||
private RecyclerViewProxy recyclerViewProxy;
|
||||
|
||||
public DiscreteScrollLayoutManager(
|
||||
@NonNull Context c,
|
||||
@NonNull ScrollStateListener scrollStateListener,
|
||||
@NonNull DSVOrientation orientation) {
|
||||
this.context = c;
|
||||
this.timeForItemSettle = DEFAULT_TIME_FOR_ITEM_SETTLE;
|
||||
this.pendingPosition = NO_POSITION;
|
||||
this.currentPosition = NO_POSITION;
|
||||
this.flingThreshold = DEFAULT_FLING_THRESHOLD;
|
||||
this.shouldSlideOnFling = false;
|
||||
this.recyclerCenter = new Point();
|
||||
this.currentViewCenter = new Point();
|
||||
this.viewCenterIterator = new Point();
|
||||
this.detachedCache = new SparseArray<>();
|
||||
this.scrollStateListener = scrollStateListener;
|
||||
this.orientationHelper = orientation.createHelper();
|
||||
this.recyclerViewProxy = new RecyclerViewProxy(this);
|
||||
this.transformClampItemCount = DEFAULT_TRANSFORM_CLAMP_ITEM_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
if (state.getItemCount() == 0) {
|
||||
recyclerViewProxy.removeAndRecycleAllViews(recycler);
|
||||
currentPosition = pendingPosition = NO_POSITION;
|
||||
scrolled = pendingScroll = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ensureValidPosition(state);
|
||||
|
||||
updateRecyclerDimensions(state);
|
||||
|
||||
//onLayoutChildren may be called multiple times and this check is required so that the flag
|
||||
//won't be cleared until onLayoutCompleted
|
||||
if (!isFirstOrEmptyLayout) {
|
||||
isFirstOrEmptyLayout = recyclerViewProxy.getChildCount() == 0;
|
||||
if (isFirstOrEmptyLayout) {
|
||||
initChildDimensions(recycler);
|
||||
}
|
||||
}
|
||||
|
||||
recyclerViewProxy.detachAndScrapAttachedViews(recycler);
|
||||
|
||||
fill(recycler);
|
||||
|
||||
applyItemTransformToChildren();
|
||||
}
|
||||
|
||||
private void ensureValidPosition(RecyclerView.State state) {
|
||||
if (currentPosition == NO_POSITION || currentPosition >= state.getItemCount()) {
|
||||
//currentPosition might have been assigned in onRestoreInstanceState()
|
||||
//which can lead to a crash (position out of bounds) when data set
|
||||
//is not persisted across rotations
|
||||
currentPosition = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutCompleted(RecyclerView.State state) {
|
||||
if (isFirstOrEmptyLayout) {
|
||||
scrollStateListener.onCurrentViewFirstLayout();
|
||||
isFirstOrEmptyLayout = false;
|
||||
} else if (dataSetChangeShiftedPosition) {
|
||||
scrollStateListener.onDataSetChangeChangedPosition();
|
||||
dataSetChangeShiftedPosition = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void initChildDimensions(RecyclerView.Recycler recycler) {
|
||||
View viewToMeasure = recyclerViewProxy.getMeasuredChildForAdapterPosition(0, recycler);
|
||||
|
||||
int childViewWidth = recyclerViewProxy.getMeasuredWidthWithMargin(viewToMeasure);
|
||||
int childViewHeight = recyclerViewProxy.getMeasuredHeightWithMargin(viewToMeasure);
|
||||
|
||||
childHalfWidth = childViewWidth / 2;
|
||||
childHalfHeight = childViewHeight / 2;
|
||||
|
||||
scrollToChangeCurrent = orientationHelper.getDistanceToChangeCurrent(
|
||||
childViewWidth,
|
||||
childViewHeight);
|
||||
|
||||
extraLayoutSpace = scrollToChangeCurrent * offscreenItems;
|
||||
|
||||
recyclerViewProxy.detachAndScrapView(viewToMeasure, recycler);
|
||||
}
|
||||
|
||||
protected void updateRecyclerDimensions(RecyclerView.State state) {
|
||||
boolean dimensionsChanged = !state.isMeasuring()
|
||||
&& (recyclerViewProxy.getWidth() != viewWidth
|
||||
|| recyclerViewProxy.getHeight() != viewHeight);
|
||||
if (dimensionsChanged) {
|
||||
viewWidth = recyclerViewProxy.getWidth();
|
||||
viewHeight = recyclerViewProxy.getHeight();
|
||||
recyclerViewProxy.removeAllViews();
|
||||
}
|
||||
recyclerCenter.set(
|
||||
recyclerViewProxy.getWidth() / 523*330,
|
||||
recyclerViewProxy.getHeight() / 2);
|
||||
}
|
||||
|
||||
protected void fill(RecyclerView.Recycler recycler) {
|
||||
cacheAndDetachAttachedViews();
|
||||
|
||||
orientationHelper.setCurrentViewCenter(recyclerCenter, scrolled, currentViewCenter);
|
||||
|
||||
final int endBound = orientationHelper.getViewEnd(
|
||||
recyclerViewProxy.getWidth(),
|
||||
recyclerViewProxy.getHeight());
|
||||
|
||||
//Layout current
|
||||
if (isViewVisible(currentViewCenter, endBound)) {
|
||||
layoutView(recycler, currentPosition, currentViewCenter);
|
||||
}
|
||||
|
||||
//Layout items before the current item
|
||||
layoutViews(recycler, Direction.START, endBound);
|
||||
|
||||
//Layout items after the current item
|
||||
layoutViews(recycler, Direction.END, endBound);
|
||||
|
||||
recycleDetachedViewsAndClearCache(recycler);
|
||||
}
|
||||
|
||||
private void layoutViews(RecyclerView.Recycler recycler, Direction direction, int endBound) {
|
||||
final int positionStep = direction.applyTo(1);
|
||||
|
||||
//Predictive layout is required when we are doing smooth fast scroll towards pendingPosition
|
||||
boolean noPredictiveLayoutRequired = pendingPosition == NO_POSITION
|
||||
|| !direction.sameAs(pendingPosition - currentPosition);
|
||||
|
||||
viewCenterIterator.set(currentViewCenter.x, currentViewCenter.y);
|
||||
for (int pos = currentPosition + positionStep; isInBounds(pos); pos += positionStep) {
|
||||
if (pos == pendingPosition) {
|
||||
noPredictiveLayoutRequired = true;
|
||||
}
|
||||
orientationHelper.shiftViewCenter(direction, scrollToChangeCurrent, viewCenterIterator);
|
||||
if (isViewVisible(viewCenterIterator, endBound)) {
|
||||
layoutView(recycler, pos, viewCenterIterator);
|
||||
} else if (noPredictiveLayoutRequired) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void layoutView(RecyclerView.Recycler recycler, int position, Point viewCenter) {
|
||||
if (position < 0) return;
|
||||
View v = detachedCache.get(position);
|
||||
if (v == null) {
|
||||
v = recyclerViewProxy.getMeasuredChildForAdapterPosition(position, recycler);
|
||||
recyclerViewProxy.layoutDecoratedWithMargins(v,
|
||||
viewCenter.x - childHalfWidth, viewCenter.y - childHalfHeight,
|
||||
viewCenter.x + childHalfWidth, viewCenter.y + childHalfHeight);
|
||||
} else {
|
||||
recyclerViewProxy.attachView(v);
|
||||
detachedCache.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
protected void cacheAndDetachAttachedViews() {
|
||||
detachedCache.clear();
|
||||
for (int i = 0; i < recyclerViewProxy.getChildCount(); i++) {
|
||||
View child = recyclerViewProxy.getChildAt(i);
|
||||
detachedCache.put(recyclerViewProxy.getPosition(child), child);
|
||||
}
|
||||
|
||||
for (int i = 0; i < detachedCache.size(); i++) {
|
||||
recyclerViewProxy.detachView(detachedCache.valueAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected void recycleDetachedViewsAndClearCache(RecyclerView.Recycler recycler) {
|
||||
for (int i = 0; i < detachedCache.size(); i++) {
|
||||
View viewToRemove = detachedCache.valueAt(i);
|
||||
recyclerViewProxy.recycleView(viewToRemove, recycler);
|
||||
}
|
||||
detachedCache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
int newPosition = currentPosition;
|
||||
if (currentPosition == NO_POSITION) {
|
||||
newPosition = 0;
|
||||
} else if (currentPosition >= positionStart) {
|
||||
newPosition = Math.min(currentPosition + itemCount, recyclerViewProxy.getItemCount() - 1);
|
||||
}
|
||||
onNewPosition(newPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
int newPosition = currentPosition;
|
||||
if (recyclerViewProxy.getItemCount() == 0) {
|
||||
newPosition = NO_POSITION;
|
||||
} else if (currentPosition >= positionStart) {
|
||||
if (currentPosition < positionStart + itemCount) {
|
||||
//If currentPosition is in the removed items, then the new item became current
|
||||
currentPosition = NO_POSITION;
|
||||
}
|
||||
newPosition = Math.max(0, currentPosition - itemCount);
|
||||
}
|
||||
onNewPosition(newPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsChanged(RecyclerView recyclerView) {
|
||||
//notifyDataSetChanged() was called. We need to ensure that currentPosition is not out of bounds
|
||||
currentPosition = Math.min(Math.max(0, currentPosition), recyclerViewProxy.getItemCount() - 1);
|
||||
dataSetChangeShiftedPosition = true;
|
||||
}
|
||||
|
||||
private void onNewPosition(int position) {
|
||||
if (currentPosition != position) {
|
||||
currentPosition = position;
|
||||
dataSetChangeShiftedPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
return scrollBy(dx, recycler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
return scrollBy(dy, recycler);
|
||||
}
|
||||
|
||||
protected int scrollBy(int amount, RecyclerView.Recycler recycler) {
|
||||
if (recyclerViewProxy.getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Direction direction = Direction.fromDelta(amount);
|
||||
int leftToScroll = calculateAllowedScrollIn(direction);
|
||||
if (leftToScroll <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int delta = direction.applyTo(Math.min(leftToScroll, Math.abs(amount)));
|
||||
scrolled += delta;
|
||||
if (pendingScroll != 0) {
|
||||
pendingScroll -= delta;
|
||||
}
|
||||
|
||||
orientationHelper.offsetChildren(-delta, recyclerViewProxy);
|
||||
|
||||
if (orientationHelper.hasNewBecomeVisible(this)) {
|
||||
fill(recycler);
|
||||
}
|
||||
|
||||
notifyScroll();
|
||||
|
||||
applyItemTransformToChildren();
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
protected void applyItemTransformToChildren() {
|
||||
if (itemTransformer != null) {
|
||||
int clampAfterDistance = scrollToChangeCurrent * transformClampItemCount;
|
||||
for (int i = 0; i < recyclerViewProxy.getChildCount(); i++) {
|
||||
View child = recyclerViewProxy.getChildAt(i);
|
||||
float position = getCenterRelativePositionOf(child, clampAfterDistance);
|
||||
itemTransformer.transformItem(child, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToPosition(int position) {
|
||||
if (currentPosition == position) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentPosition = position;
|
||||
recyclerViewProxy.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
if (currentPosition == position || pendingPosition != NO_POSITION) {
|
||||
return;
|
||||
}
|
||||
checkTargetPosition(state, position);
|
||||
if (currentPosition == NO_POSITION) {
|
||||
//Layout not happened yet
|
||||
currentPosition = position;
|
||||
} else {
|
||||
startSmoothPendingScroll(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return orientationHelper.canScrollHorizontally();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return orientationHelper.canScrollVertically();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(int state) {
|
||||
if (currentScrollState == RecyclerView.SCROLL_STATE_IDLE && currentScrollState != state) {
|
||||
scrollStateListener.onScrollStart();
|
||||
}
|
||||
|
||||
if (state == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
//Scroll is not finished until current view is centered
|
||||
boolean isScrollEnded = onScrollEnd();
|
||||
if (isScrollEnded) {
|
||||
scrollStateListener.onScrollEnd();
|
||||
} else {
|
||||
//Scroll continues and we don't want to set currentScrollState to STATE_IDLE,
|
||||
//because this will then trigger .scrollStateListener.onScrollStart()
|
||||
return;
|
||||
}
|
||||
} else if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
onDragStart();
|
||||
}
|
||||
currentScrollState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if scroll is ended and we don't need to settle items
|
||||
*/
|
||||
private boolean onScrollEnd() {
|
||||
if (pendingPosition != NO_POSITION) {
|
||||
currentPosition = pendingPosition;
|
||||
pendingPosition = NO_POSITION;
|
||||
scrolled = 0;
|
||||
}
|
||||
|
||||
Direction scrollDirection = Direction.fromDelta(scrolled);
|
||||
if (Math.abs(scrolled) == scrollToChangeCurrent) {
|
||||
currentPosition += scrollDirection.applyTo(1);
|
||||
scrolled = 0;
|
||||
}
|
||||
|
||||
if (isAnotherItemCloserThanCurrent()) {
|
||||
pendingScroll = getHowMuchIsLeftToScroll(scrolled);
|
||||
} else {
|
||||
pendingScroll = -scrolled;
|
||||
}
|
||||
|
||||
if (pendingScroll == 0) {
|
||||
return true;
|
||||
} else {
|
||||
startSmoothPendingScroll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDragStart() {
|
||||
//Here we need to:
|
||||
//1. Stop any pending scroll
|
||||
//2. Set currentPosition to position of the item that is closest to the center
|
||||
boolean isScrollingThroughMultiplePositions = Math.abs(scrolled) > scrollToChangeCurrent;
|
||||
if (isScrollingThroughMultiplePositions) {
|
||||
int scrolledPositions = scrolled / scrollToChangeCurrent;
|
||||
currentPosition += scrolledPositions;
|
||||
scrolled -= scrolledPositions * scrollToChangeCurrent;
|
||||
}
|
||||
if (isAnotherItemCloserThanCurrent()) {
|
||||
Direction direction = Direction.fromDelta(scrolled);
|
||||
currentPosition += direction.applyTo(1);
|
||||
scrolled = -getHowMuchIsLeftToScroll(scrolled);
|
||||
}
|
||||
pendingPosition = NO_POSITION;
|
||||
pendingScroll = 0;
|
||||
}
|
||||
|
||||
public void onFling(int velocityX, int velocityY) {
|
||||
int velocity = orientationHelper.getFlingVelocity(velocityX, velocityY);
|
||||
int throttleValue = shouldSlideOnFling ? Math.abs(velocity / flingThreshold) : 1;
|
||||
int newPosition = currentPosition + Direction.fromDelta(velocity).applyTo(throttleValue);
|
||||
newPosition = checkNewOnFlingPositionIsInBounds(newPosition);
|
||||
boolean isInScrollDirection = velocity * scrolled >= 0;
|
||||
boolean canFling = isInScrollDirection && isInBounds(newPosition);
|
||||
if (canFling) {
|
||||
startSmoothPendingScroll(newPosition);
|
||||
} else {
|
||||
returnToCurrentPosition();
|
||||
}
|
||||
}
|
||||
|
||||
public void returnToCurrentPosition() {
|
||||
pendingScroll = -scrolled;
|
||||
if (pendingScroll != 0) {
|
||||
startSmoothPendingScroll();
|
||||
}
|
||||
}
|
||||
|
||||
protected int calculateAllowedScrollIn(Direction direction) {
|
||||
if (pendingScroll != 0) {
|
||||
return Math.abs(pendingScroll);
|
||||
}
|
||||
int allowedScroll;
|
||||
boolean isBoundReached;
|
||||
boolean isScrollDirectionAsBefore = direction.applyTo(scrolled) > 0;
|
||||
if (direction == Direction.START && currentPosition == 0) {
|
||||
//We can scroll to the left when currentPosition == 0 only if we scrolled to the right before
|
||||
isBoundReached = scrolled == 0;
|
||||
allowedScroll = isBoundReached ? 0 : Math.abs(scrolled);
|
||||
} else if (direction == Direction.END && currentPosition == recyclerViewProxy.getItemCount() - 1) {
|
||||
//We can scroll to the right when currentPosition == last only if we scrolled to the left before
|
||||
isBoundReached = scrolled == 0;
|
||||
allowedScroll = isBoundReached ? 0 : Math.abs(scrolled);
|
||||
} else {
|
||||
isBoundReached = false;
|
||||
allowedScroll = isScrollDirectionAsBefore ?
|
||||
scrollToChangeCurrent - Math.abs(scrolled) :
|
||||
scrollToChangeCurrent + Math.abs(scrolled);
|
||||
}
|
||||
scrollStateListener.onIsBoundReachedFlagChange(isBoundReached);
|
||||
return allowedScroll;
|
||||
}
|
||||
|
||||
private void startSmoothPendingScroll() {
|
||||
LinearSmoothScroller scroller = new DiscreteLinearSmoothScroller(context);
|
||||
scroller.setTargetPosition(currentPosition);
|
||||
recyclerViewProxy.startSmoothScroll(scroller);
|
||||
}
|
||||
|
||||
private void startSmoothPendingScroll(int position) {
|
||||
if (currentPosition == position) return;
|
||||
pendingScroll = -scrolled;
|
||||
Direction direction = Direction.fromDelta(position - currentPosition);
|
||||
int distanceToScroll = Math.abs(position - currentPosition) * scrollToChangeCurrent;
|
||||
pendingScroll += direction.applyTo(distanceToScroll);
|
||||
pendingPosition = position;
|
||||
startSmoothPendingScroll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoMeasureEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeVerticalScrollRange(RecyclerView.State state) {
|
||||
return computeScrollRange(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeVerticalScrollOffset(RecyclerView.State state) {
|
||||
return computeScrollOffset(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeVerticalScrollExtent(RecyclerView.State state) {
|
||||
return computeScrollExtent(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeHorizontalScrollRange(RecyclerView.State state) {
|
||||
return computeScrollRange(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeHorizontalScrollOffset(RecyclerView.State state) {
|
||||
return computeScrollOffset(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeHorizontalScrollExtent(RecyclerView.State state) {
|
||||
return computeScrollExtent(state);
|
||||
}
|
||||
|
||||
private int computeScrollOffset(RecyclerView.State state) {
|
||||
int scrollbarSize = computeScrollExtent(state);
|
||||
int offset = (int) ((scrolled / (float) scrollToChangeCurrent) * scrollbarSize);
|
||||
return (currentPosition * scrollbarSize) + offset;
|
||||
}
|
||||
|
||||
private int computeScrollExtent(RecyclerView.State state) {
|
||||
if (getItemCount() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (int) (computeScrollRange(state) / (float) getItemCount());
|
||||
}
|
||||
}
|
||||
|
||||
private int computeScrollRange(RecyclerView.State state) {
|
||||
if (getItemCount() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return scrollToChangeCurrent * (getItemCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
|
||||
pendingPosition = NO_POSITION;
|
||||
scrolled = pendingScroll = 0;
|
||||
if (newAdapter instanceof InitialPositionProvider) {
|
||||
currentPosition = ((InitialPositionProvider) newAdapter).getInitialPosition();
|
||||
} else {
|
||||
currentPosition = 0;
|
||||
}
|
||||
recyclerViewProxy.removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
Bundle bundle = new Bundle();
|
||||
if (pendingPosition != NO_POSITION) {
|
||||
currentPosition = pendingPosition;
|
||||
}
|
||||
bundle.putInt(EXTRA_POSITION, currentPosition);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
currentPosition = bundle.getInt(EXTRA_POSITION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new RecyclerView.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
public int getNextPosition() {
|
||||
if (scrolled == 0) {
|
||||
return currentPosition;
|
||||
} else if (pendingPosition != NO_POSITION) {
|
||||
return pendingPosition;
|
||||
} else {
|
||||
return currentPosition + Direction.fromDelta(scrolled).applyTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void setItemTransformer(DiscreteScrollItemTransformer itemTransformer) {
|
||||
this.itemTransformer = itemTransformer;
|
||||
}
|
||||
|
||||
public void setTimeForItemSettle(int timeForItemSettle) {
|
||||
this.timeForItemSettle = timeForItemSettle;
|
||||
}
|
||||
|
||||
public void setOffscreenItems(int offscreenItems) {
|
||||
this.offscreenItems = offscreenItems;
|
||||
extraLayoutSpace = scrollToChangeCurrent * offscreenItems;
|
||||
recyclerViewProxy.requestLayout();
|
||||
}
|
||||
|
||||
public void setTransformClampItemCount(int transformClampItemCount) {
|
||||
this.transformClampItemCount = transformClampItemCount;
|
||||
applyItemTransformToChildren();
|
||||
}
|
||||
|
||||
public void setOrientation(DSVOrientation orientation) {
|
||||
orientationHelper = orientation.createHelper();
|
||||
recyclerViewProxy.removeAllViews();
|
||||
recyclerViewProxy.requestLayout();
|
||||
}
|
||||
|
||||
public void setShouldSlideOnFling(boolean result) {
|
||||
shouldSlideOnFling = result;
|
||||
}
|
||||
|
||||
public void setSlideOnFlingThreshold(int threshold) {
|
||||
flingThreshold = threshold;
|
||||
}
|
||||
|
||||
public int getCurrentPosition() {
|
||||
return currentPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
|
||||
super.onInitializeAccessibilityEvent(event);
|
||||
if (recyclerViewProxy.getChildCount() > 0) {
|
||||
final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
|
||||
record.setFromIndex(getPosition(getFirstChild()));
|
||||
record.setToIndex(getPosition(getLastChild()));
|
||||
}
|
||||
}
|
||||
|
||||
private float getCenterRelativePositionOf(View v, int maxDistance) {
|
||||
float distanceFromCenter = orientationHelper.getDistanceFromCenter(recyclerCenter,
|
||||
getDecoratedLeft(v) + childHalfWidth,
|
||||
getDecoratedTop(v) + childHalfHeight);
|
||||
return Math.min(Math.max(-1f, distanceFromCenter / maxDistance), 1f);
|
||||
}
|
||||
|
||||
private int checkNewOnFlingPositionIsInBounds(int position) {
|
||||
final int itemCount = recyclerViewProxy.getItemCount();
|
||||
//The check is required in case slide through multiple items is turned on
|
||||
if (currentPosition != 0 && position < 0) {
|
||||
//If currentPosition == 0 && position < 0 we forbid scroll to the left,
|
||||
//but if currentPosition != 0 we can slide to the first item
|
||||
return 0;
|
||||
} else if (currentPosition != itemCount - 1 && position >= itemCount) {
|
||||
return itemCount - 1;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
private int getHowMuchIsLeftToScroll(int dx) {
|
||||
return Direction.fromDelta(dx).applyTo(scrollToChangeCurrent - Math.abs(scrolled));
|
||||
}
|
||||
|
||||
private boolean isAnotherItemCloserThanCurrent() {
|
||||
return Math.abs(scrolled) >= scrollToChangeCurrent * SCROLL_TO_SNAP_TO_ANOTHER_ITEM;
|
||||
}
|
||||
|
||||
public View getFirstChild() {
|
||||
return recyclerViewProxy.getChildAt(0);
|
||||
}
|
||||
|
||||
public View getLastChild() {
|
||||
return recyclerViewProxy.getChildAt(recyclerViewProxy.getChildCount() - 1);
|
||||
}
|
||||
|
||||
public int getExtraLayoutSpace() {
|
||||
return extraLayoutSpace;
|
||||
}
|
||||
|
||||
private void notifyScroll() {
|
||||
float amountToScroll = pendingPosition != NO_POSITION ?
|
||||
Math.abs(scrolled + pendingScroll) :
|
||||
scrollToChangeCurrent;
|
||||
float position = -Math.min(Math.max(-1f, scrolled / amountToScroll), 1f);
|
||||
scrollStateListener.onScroll(position);
|
||||
}
|
||||
|
||||
private boolean isInBounds(int itemPosition) {
|
||||
return itemPosition >= 0 && itemPosition < recyclerViewProxy.getItemCount();
|
||||
}
|
||||
|
||||
private boolean isViewVisible(Point viewCenter, int endBound) {
|
||||
return orientationHelper.isViewVisible(
|
||||
viewCenter, childHalfWidth, childHalfHeight,
|
||||
endBound, extraLayoutSpace);
|
||||
}
|
||||
|
||||
private void checkTargetPosition(RecyclerView.State state, int targetPosition) {
|
||||
if (targetPosition < 0 || targetPosition >= state.getItemCount()) {
|
||||
throw new IllegalArgumentException(String.format(Locale.US,
|
||||
"target position out of bounds: position=%d, itemCount=%d",
|
||||
targetPosition, state.getItemCount()));
|
||||
}
|
||||
}
|
||||
|
||||
protected void setRecyclerViewProxy(RecyclerViewProxy recyclerViewProxy) {
|
||||
this.recyclerViewProxy = recyclerViewProxy;
|
||||
}
|
||||
|
||||
protected void setOrientationHelper(DSVOrientation.Helper orientationHelper) {
|
||||
this.orientationHelper = orientationHelper;
|
||||
}
|
||||
|
||||
private class DiscreteLinearSmoothScroller extends LinearSmoothScroller {
|
||||
|
||||
public DiscreteLinearSmoothScroller(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateDxToMakeVisible(View view, int snapPreference) {
|
||||
return orientationHelper.getPendingDx(-pendingScroll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateDyToMakeVisible(View view, int snapPreference) {
|
||||
return orientationHelper.getPendingDy(-pendingScroll);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int calculateTimeForScrolling(int dx) {
|
||||
float dist = Math.min(Math.abs(dx), scrollToChangeCurrent);
|
||||
return (int) (Math.max(0.01f, dist / scrollToChangeCurrent) * timeForItemSettle);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
return new PointF(
|
||||
orientationHelper.getPendingDx(pendingScroll),
|
||||
orientationHelper.getPendingDy(pendingScroll));
|
||||
}
|
||||
}
|
||||
|
||||
public interface ScrollStateListener {
|
||||
void onIsBoundReachedFlagChange(boolean isBoundReached);
|
||||
|
||||
void onScrollStart();
|
||||
|
||||
void onScrollEnd();
|
||||
|
||||
void onScroll(float currentViewPosition);
|
||||
|
||||
void onCurrentViewFirstLayout();
|
||||
|
||||
void onDataSetChangeChangedPosition();
|
||||
}
|
||||
|
||||
public interface InitialPositionProvider {
|
||||
int getInitialPosition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.yarolegovich.discretescrollview.transform.DiscreteScrollItemTransformer;
|
||||
import com.yarolegovich.discretescrollview.util.ScrollListenerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 18.02.2017.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DiscreteScrollView extends RecyclerView {
|
||||
|
||||
public static final int NO_POSITION = DiscreteScrollLayoutManager.NO_POSITION;
|
||||
|
||||
private static final int DEFAULT_ORIENTATION = DSVOrientation.HORIZONTAL.ordinal();
|
||||
|
||||
private DiscreteScrollLayoutManager layoutManager;
|
||||
|
||||
private List<ScrollStateChangeListener> scrollStateChangeListeners;
|
||||
private List<OnItemChangedListener> onItemChangedListeners;
|
||||
|
||||
private boolean isOverScrollEnabled;
|
||||
|
||||
public DiscreteScrollView(Context context) {
|
||||
super(context);
|
||||
init(null);
|
||||
}
|
||||
|
||||
public DiscreteScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
public DiscreteScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
private void init(AttributeSet attrs) {
|
||||
scrollStateChangeListeners = new ArrayList<>();
|
||||
onItemChangedListeners = new ArrayList<>();
|
||||
|
||||
int orientation = DEFAULT_ORIENTATION;
|
||||
if (attrs != null) {
|
||||
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.DiscreteScrollView);
|
||||
orientation = ta.getInt(R.styleable.DiscreteScrollView_dsv_orientation, DEFAULT_ORIENTATION);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
isOverScrollEnabled = getOverScrollMode() != OVER_SCROLL_NEVER;
|
||||
|
||||
layoutManager = new DiscreteScrollLayoutManager(
|
||||
getContext(), new ScrollStateListener(),
|
||||
DSVOrientation.values()[orientation]);
|
||||
setLayoutManager(layoutManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayoutManager(LayoutManager layout) {
|
||||
if (layout instanceof DiscreteScrollLayoutManager) {
|
||||
super.setLayoutManager(layout);
|
||||
} else {
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.dsv_ex_msg_dont_set_lm));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean fling(int velocityX, int velocityY) {
|
||||
boolean isFling = super.fling(velocityX, velocityY);
|
||||
if (isFling) {
|
||||
layoutManager.onFling(velocityX, velocityY);
|
||||
} else {
|
||||
layoutManager.returnToCurrentPosition();
|
||||
}
|
||||
return isFling;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ViewHolder getViewHolder(int position) {
|
||||
View view = layoutManager.findViewByPosition(position);
|
||||
return view != null ? getChildViewHolder(view) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return adapter position of the current item or -1 if nothing is selected
|
||||
*/
|
||||
public int getCurrentItem() {
|
||||
return layoutManager.getCurrentPosition();
|
||||
}
|
||||
|
||||
public void setItemTransformer(DiscreteScrollItemTransformer transformer) {
|
||||
layoutManager.setItemTransformer(transformer);
|
||||
}
|
||||
|
||||
public void setItemTransitionTimeMillis(@IntRange(from = 10) int millis) {
|
||||
layoutManager.setTimeForItemSettle(millis);
|
||||
}
|
||||
|
||||
public void setSlideOnFling(boolean result){
|
||||
layoutManager.setShouldSlideOnFling(result);
|
||||
}
|
||||
|
||||
public void setSlideOnFlingThreshold(int threshold){
|
||||
layoutManager.setSlideOnFlingThreshold(threshold);
|
||||
}
|
||||
|
||||
public void setOrientation(DSVOrientation orientation) {
|
||||
layoutManager.setOrientation(orientation);
|
||||
}
|
||||
|
||||
public void setOffscreenItems(int items) {
|
||||
layoutManager.setOffscreenItems(items);
|
||||
}
|
||||
|
||||
public void setClampTransformProgressAfter(@IntRange(from = 1) int itemCount) {
|
||||
if (itemCount <= 1) {
|
||||
throw new IllegalArgumentException("must be >= 1");
|
||||
}
|
||||
layoutManager.setTransformClampItemCount(itemCount);
|
||||
}
|
||||
|
||||
public void setOverScrollEnabled(boolean overScrollEnabled) {
|
||||
isOverScrollEnabled = overScrollEnabled;
|
||||
setOverScrollMode(OVER_SCROLL_NEVER);
|
||||
}
|
||||
|
||||
public void addScrollStateChangeListener(@NonNull ScrollStateChangeListener<?> scrollStateChangeListener) {
|
||||
scrollStateChangeListeners.add(scrollStateChangeListener);
|
||||
}
|
||||
|
||||
public void addScrollListener(@NonNull ScrollListener<?> scrollListener) {
|
||||
addScrollStateChangeListener(new ScrollListenerAdapter(scrollListener));
|
||||
}
|
||||
|
||||
public void addOnItemChangedListener(@NonNull OnItemChangedListener<?> onItemChangedListener) {
|
||||
onItemChangedListeners.add(onItemChangedListener);
|
||||
}
|
||||
|
||||
public void removeScrollStateChangeListener(@NonNull ScrollStateChangeListener<?> scrollStateChangeListener) {
|
||||
scrollStateChangeListeners.remove(scrollStateChangeListener);
|
||||
}
|
||||
|
||||
public void removeScrollListener(@NonNull ScrollListener<?> scrollListener) {
|
||||
removeScrollStateChangeListener(new ScrollListenerAdapter<>(scrollListener));
|
||||
}
|
||||
|
||||
public void removeItemChangedListener(@NonNull OnItemChangedListener<?> onItemChangedListener) {
|
||||
onItemChangedListeners.remove(onItemChangedListener);
|
||||
}
|
||||
|
||||
private void notifyScrollStart(ViewHolder holder, int current) {
|
||||
for (ScrollStateChangeListener listener : scrollStateChangeListeners) {
|
||||
listener.onScrollStart(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyScrollEnd(ViewHolder holder, int current) {
|
||||
for (ScrollStateChangeListener listener : scrollStateChangeListeners) {
|
||||
listener.onScrollEnd(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyScroll(float position,
|
||||
int currentIndex, int newIndex,
|
||||
ViewHolder currentHolder, ViewHolder newHolder) {
|
||||
for (ScrollStateChangeListener listener : scrollStateChangeListeners) {
|
||||
listener.onScroll(position, currentIndex, newIndex,
|
||||
currentHolder,
|
||||
newHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCurrentItemChanged(ViewHolder holder, int current) {
|
||||
for (OnItemChangedListener listener : onItemChangedListeners) {
|
||||
listener.onCurrentItemChanged(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCurrentItemChanged() {
|
||||
if (onItemChangedListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int current = layoutManager.getCurrentPosition();
|
||||
ViewHolder currentHolder = getViewHolder(current);
|
||||
notifyCurrentItemChanged(currentHolder, current);
|
||||
}
|
||||
|
||||
private class ScrollStateListener implements DiscreteScrollLayoutManager.ScrollStateListener {
|
||||
|
||||
@Override
|
||||
public void onIsBoundReachedFlagChange(boolean isBoundReached) {
|
||||
if (isOverScrollEnabled) {
|
||||
setOverScrollMode(isBoundReached ? OVER_SCROLL_ALWAYS : OVER_SCROLL_NEVER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStart() {
|
||||
if (scrollStateChangeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int current = layoutManager.getCurrentPosition();
|
||||
ViewHolder holder = getViewHolder(current);
|
||||
if (holder != null) {
|
||||
notifyScrollStart(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollEnd() {
|
||||
if (onItemChangedListeners.isEmpty() && scrollStateChangeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int current = layoutManager.getCurrentPosition();
|
||||
ViewHolder holder = getViewHolder(current);
|
||||
if (holder != null) {
|
||||
notifyScrollEnd(holder, current);
|
||||
notifyCurrentItemChanged(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(float currentViewPosition) {
|
||||
if (scrollStateChangeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int currentIndex = getCurrentItem();
|
||||
int newIndex = layoutManager.getNextPosition();
|
||||
if (currentIndex != newIndex) {
|
||||
notifyScroll(currentViewPosition,
|
||||
currentIndex, newIndex,
|
||||
getViewHolder(currentIndex),
|
||||
getViewHolder(newIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentViewFirstLayout() {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
notifyCurrentItemChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataSetChangeChangedPosition() {
|
||||
notifyCurrentItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ScrollStateChangeListener<T extends ViewHolder> {
|
||||
|
||||
void onScrollStart(@NonNull T currentItemHolder, int adapterPosition);
|
||||
|
||||
void onScrollEnd(@NonNull T currentItemHolder, int adapterPosition);
|
||||
|
||||
void onScroll(float scrollPosition,
|
||||
int currentPosition,
|
||||
int newPosition,
|
||||
@Nullable T currentHolder,
|
||||
@Nullable T newCurrent);
|
||||
}
|
||||
|
||||
public interface ScrollListener<T extends ViewHolder> {
|
||||
|
||||
void onScroll(float scrollPosition,
|
||||
int currentPosition, int newPosition,
|
||||
@Nullable T currentHolder,
|
||||
@Nullable T newCurrent);
|
||||
}
|
||||
|
||||
public interface OnItemChangedListener<T extends ViewHolder> {
|
||||
/*
|
||||
* This method will be also triggered when view appears on the screen for the first time.
|
||||
* If data set is empty, viewHolder will be null and adapterPosition will be NO_POSITION
|
||||
*/
|
||||
void onCurrentItemChanged(@Nullable T viewHolder, int adapterPosition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 28-Apr-17.
|
||||
*/
|
||||
|
||||
public class InfiniteScrollAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T>
|
||||
implements DiscreteScrollLayoutManager.InitialPositionProvider {
|
||||
|
||||
private static final int CENTER = Integer.MAX_VALUE / 2;
|
||||
private static final int RESET_BOUND = 100;
|
||||
|
||||
public static <T extends RecyclerView.ViewHolder> InfiniteScrollAdapter<T> wrap(
|
||||
@NonNull RecyclerView.Adapter<T> adapter) {
|
||||
return new InfiniteScrollAdapter<>(adapter);
|
||||
}
|
||||
|
||||
private RecyclerView.Adapter<T> wrapped;
|
||||
private DiscreteScrollLayoutManager layoutManager;
|
||||
|
||||
public InfiniteScrollAdapter(@NonNull RecyclerView.Adapter<T> wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
this.wrapped.registerAdapterDataObserver(new DataSetChangeDelegate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
wrapped.onAttachedToRecyclerView(recyclerView);
|
||||
if (recyclerView instanceof DiscreteScrollView) {
|
||||
layoutManager = (DiscreteScrollLayoutManager) recyclerView.getLayoutManager();
|
||||
} else {
|
||||
String msg = recyclerView.getContext().getString(R.string.dsv_ex_msg_adapter_wrong_recycler);
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
wrapped.onDetachedFromRecyclerView(recyclerView);
|
||||
layoutManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull T onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return wrapped.onCreateViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull T holder, int position) {
|
||||
if (isResetRequired(position)) {
|
||||
int resetPosition = CENTER + mapPositionToReal(layoutManager.getCurrentPosition());
|
||||
setPosition(resetPosition);
|
||||
return;
|
||||
}
|
||||
wrapped.onBindViewHolder(holder, mapPositionToReal(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return wrapped.getItemViewType(mapPositionToReal(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return isInfinite() ? Integer.MAX_VALUE : wrapped.getItemCount();
|
||||
}
|
||||
|
||||
public int getRealItemCount() {
|
||||
return wrapped.getItemCount();
|
||||
}
|
||||
|
||||
public int getRealCurrentPosition() {
|
||||
return getRealPosition(layoutManager.getCurrentPosition());
|
||||
}
|
||||
|
||||
public int getRealPosition(int position) {
|
||||
return mapPositionToReal(position);
|
||||
}
|
||||
|
||||
public int getClosestPosition(int position) {
|
||||
ensureValidPosition(position);
|
||||
int adapterCurrent = layoutManager.getCurrentPosition();
|
||||
int current = mapPositionToReal(adapterCurrent);
|
||||
if (position == current) {
|
||||
return adapterCurrent;
|
||||
}
|
||||
int delta = position - current;
|
||||
int target = adapterCurrent + delta;
|
||||
int wraparoundTarget = adapterCurrent + (position > current ?
|
||||
delta - wrapped.getItemCount() :
|
||||
wrapped.getItemCount() + delta);
|
||||
int distance = Math.abs(adapterCurrent - target);
|
||||
int wraparoundDistance = Math.abs(adapterCurrent - wraparoundTarget);
|
||||
if (distance == wraparoundDistance) {
|
||||
//Scroll to the right feels more natural, so prefer it
|
||||
return target > adapterCurrent ? target : wraparoundTarget;
|
||||
} else {
|
||||
return distance < wraparoundDistance ? target : wraparoundTarget;
|
||||
}
|
||||
}
|
||||
|
||||
private int mapPositionToReal(int position) {
|
||||
if (position < CENTER) {
|
||||
int rem = (CENTER - position) % wrapped.getItemCount();
|
||||
return rem == 0 ? 0 : wrapped.getItemCount() - rem;
|
||||
} else {
|
||||
return (position - CENTER) % wrapped.getItemCount();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isResetRequired(int requestedPosition) {
|
||||
return isInfinite()
|
||||
&& (requestedPosition <= RESET_BOUND
|
||||
|| requestedPosition >= (Integer.MAX_VALUE - RESET_BOUND));
|
||||
}
|
||||
|
||||
private void ensureValidPosition(int position) {
|
||||
if (position >= wrapped.getItemCount()) {
|
||||
throw new IndexOutOfBoundsException(String.format(Locale.US,
|
||||
"requested position is outside adapter's bounds: position=%d, size=%d",
|
||||
position, wrapped.getItemCount()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInfinite() {
|
||||
return wrapped.getItemCount() > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialPosition() {
|
||||
return isInfinite() ? CENTER : 0;
|
||||
}
|
||||
|
||||
private void setPosition(int position) {
|
||||
layoutManager.scrollToPosition(position);
|
||||
}
|
||||
|
||||
//TODO: handle proper data set change notifications
|
||||
private class DataSetChangeDelegate extends RecyclerView.AdapterDataObserver {
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
setPosition(getInitialPosition());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
notifyItemRangeChanged(0, getItemCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
notifyItemRangeChanged(0, getItemCount(), payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 10/25/17.
|
||||
*/
|
||||
public class RecyclerViewProxy {
|
||||
|
||||
private RecyclerView.LayoutManager layoutManager;
|
||||
|
||||
public RecyclerViewProxy(@NonNull RecyclerView.LayoutManager layoutManager) {
|
||||
this.layoutManager = layoutManager;
|
||||
}
|
||||
|
||||
public void attachView(View view) {
|
||||
layoutManager.attachView(view);
|
||||
}
|
||||
|
||||
public void detachView(View view) {
|
||||
layoutManager.detachView(view);
|
||||
}
|
||||
|
||||
public void detachAndScrapView(View view, RecyclerView.Recycler recycler) {
|
||||
layoutManager.detachAndScrapView(view, recycler);
|
||||
}
|
||||
|
||||
public void detachAndScrapAttachedViews(RecyclerView.Recycler recycler) {
|
||||
layoutManager.detachAndScrapAttachedViews(recycler);
|
||||
}
|
||||
|
||||
public void recycleView(View view, RecyclerView.Recycler recycler) {
|
||||
recycler.recycleView(view);
|
||||
}
|
||||
|
||||
public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
|
||||
layoutManager.removeAndRecycleAllViews(recycler);
|
||||
}
|
||||
|
||||
public int getChildCount() {
|
||||
return layoutManager.getChildCount();
|
||||
}
|
||||
|
||||
public int getItemCount() {
|
||||
return layoutManager.getItemCount();
|
||||
}
|
||||
|
||||
public View getMeasuredChildForAdapterPosition(int position, RecyclerView.Recycler recycler) {
|
||||
View view = recycler.getViewForPosition(position);
|
||||
layoutManager.addView(view);
|
||||
layoutManager.measureChildWithMargins(view, 0, 0);
|
||||
return view;
|
||||
}
|
||||
|
||||
public void layoutDecoratedWithMargins(View v, int left, int top, int right, int bottom) {
|
||||
layoutManager.layoutDecoratedWithMargins(v, left, top, right, bottom);
|
||||
}
|
||||
|
||||
public View getChildAt(int index) {
|
||||
return layoutManager.getChildAt(index);
|
||||
}
|
||||
|
||||
public int getPosition(View view) {
|
||||
return layoutManager.getPosition(view);
|
||||
}
|
||||
|
||||
public int getMeasuredWidthWithMargin(View child) {
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
|
||||
return layoutManager.getDecoratedMeasuredWidth(child) + lp.leftMargin + lp.rightMargin;
|
||||
}
|
||||
|
||||
public int getMeasuredHeightWithMargin(View child) {
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
|
||||
return layoutManager.getDecoratedMeasuredHeight(child) + lp.topMargin + lp.bottomMargin;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return layoutManager.getWidth();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return layoutManager.getHeight();
|
||||
}
|
||||
|
||||
public void offsetChildrenHorizontal(int amount) {
|
||||
layoutManager.offsetChildrenHorizontal(amount);
|
||||
}
|
||||
|
||||
public void offsetChildrenVertical(int amount) {
|
||||
layoutManager.offsetChildrenVertical(amount);
|
||||
}
|
||||
|
||||
public void requestLayout() {
|
||||
layoutManager.requestLayout();
|
||||
}
|
||||
|
||||
public void startSmoothScroll(RecyclerView.SmoothScroller smoothScroller) {
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
public void removeAllViews() {
|
||||
layoutManager.removeAllViews();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.yarolegovich.discretescrollview.transform;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 02.03.2017.
|
||||
*/
|
||||
|
||||
public interface DiscreteScrollItemTransformer {
|
||||
void transformItem(View item, float position);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.yarolegovich.discretescrollview.transform;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 03.03.2017.
|
||||
*/
|
||||
|
||||
public class Pivot {
|
||||
|
||||
public static final int AXIS_X = 0;
|
||||
public static final int AXIS_Y = 1;
|
||||
|
||||
private static final int PIVOT_CENTER = -1;
|
||||
private static final int PIVOT_MAX = -2;
|
||||
|
||||
private int axis;
|
||||
private int pivotPoint;
|
||||
|
||||
public Pivot(@Axis int axis, int pivotPoint) {
|
||||
this.axis = axis;
|
||||
this.pivotPoint = pivotPoint;
|
||||
}
|
||||
|
||||
public void setOn(View view) {
|
||||
if (axis == AXIS_X) {
|
||||
switch (pivotPoint) {
|
||||
case PIVOT_CENTER:
|
||||
view.setPivotX(view.getWidth() * 0.5f);
|
||||
break;
|
||||
case PIVOT_MAX:
|
||||
view.setPivotX(view.getWidth());
|
||||
break;
|
||||
default:
|
||||
view.setPivotX(pivotPoint);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (axis == AXIS_Y) {
|
||||
switch (pivotPoint) {
|
||||
case PIVOT_CENTER:
|
||||
view.setPivotY(view.getHeight() * 0.5f);
|
||||
break;
|
||||
case PIVOT_MAX:
|
||||
view.setPivotY(view.getHeight());
|
||||
break;
|
||||
default:
|
||||
view.setPivotY(pivotPoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Axis
|
||||
public int getAxis() {
|
||||
return axis;
|
||||
}
|
||||
|
||||
public enum X {
|
||||
LEFT {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_X, 0);
|
||||
}
|
||||
},
|
||||
CENTER {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_X, PIVOT_CENTER);
|
||||
}
|
||||
},
|
||||
RIGHT {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_X, PIVOT_MAX);
|
||||
}
|
||||
};
|
||||
|
||||
public abstract Pivot create();
|
||||
}
|
||||
|
||||
public enum Y {
|
||||
TOP {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_Y, 0);
|
||||
}
|
||||
},
|
||||
CENTER {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_Y, PIVOT_CENTER);
|
||||
}
|
||||
},
|
||||
BOTTOM {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_Y, PIVOT_MAX);
|
||||
}
|
||||
};
|
||||
|
||||
public abstract Pivot create();
|
||||
}
|
||||
|
||||
@IntDef({AXIS_X, AXIS_Y})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Axis{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.yarolegovich.discretescrollview.transform;
|
||||
|
||||
import android.view.View;
|
||||
import androidx.annotation.FloatRange;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 03.03.2017.
|
||||
*/
|
||||
public class ScaleTransformer implements DiscreteScrollItemTransformer {
|
||||
|
||||
private Pivot pivotX;
|
||||
private Pivot pivotY;
|
||||
private float minScale;
|
||||
private float maxMinDiff;
|
||||
|
||||
public ScaleTransformer() {
|
||||
pivotX = Pivot.X.CENTER.create();
|
||||
pivotY = Pivot.Y.CENTER.create();
|
||||
minScale = 0.8f;
|
||||
maxMinDiff = 0.2f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformItem(View item, float position) {
|
||||
pivotX.setOn(item);
|
||||
pivotY.setOn(item);
|
||||
float closenessToCenter = 1f - Math.abs(position);
|
||||
float scale = minScale + maxMinDiff * closenessToCenter;
|
||||
item.setScaleX(scale);
|
||||
item.setScaleY(scale);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private ScaleTransformer transformer;
|
||||
private float maxScale;
|
||||
|
||||
public Builder() {
|
||||
transformer = new ScaleTransformer();
|
||||
maxScale = 1f;
|
||||
}
|
||||
|
||||
public Builder setMinScale(@FloatRange(from = 0.01) float scale) {
|
||||
transformer.minScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxScale(@FloatRange(from = 0.01) float scale) {
|
||||
maxScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPivotX(Pivot.X pivotX) {
|
||||
return setPivotX(pivotX.create());
|
||||
}
|
||||
|
||||
public Builder setPivotX(Pivot pivot) {
|
||||
assertAxis(pivot, Pivot.AXIS_X);
|
||||
transformer.pivotX = pivot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPivotY(Pivot.Y pivotY) {
|
||||
return setPivotY(pivotY.create());
|
||||
}
|
||||
|
||||
public Builder setPivotY(Pivot pivot) {
|
||||
assertAxis(pivot, Pivot.AXIS_Y);
|
||||
transformer.pivotY = pivot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScaleTransformer build() {
|
||||
transformer.maxMinDiff = maxScale - transformer.minScale;
|
||||
return transformer;
|
||||
}
|
||||
|
||||
private void assertAxis(Pivot pivot, @Pivot.Axis int axis) {
|
||||
if (pivot.getAxis() != axis) {
|
||||
throw new IllegalArgumentException("You passed a Pivot for wrong axis.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.yarolegovich.discretescrollview.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.yarolegovich.discretescrollview.DiscreteScrollView;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 16.03.2017.
|
||||
*/
|
||||
public class ScrollListenerAdapter<T extends RecyclerView.ViewHolder> implements DiscreteScrollView.ScrollStateChangeListener<T> {
|
||||
|
||||
private DiscreteScrollView.ScrollListener<T> adaptee;
|
||||
|
||||
public ScrollListenerAdapter(@NonNull DiscreteScrollView.ScrollListener<T> adaptee) {
|
||||
this.adaptee = adaptee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStart(@NonNull T currentItemHolder, int adapterPosition) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollEnd(@NonNull T currentItemHolder, int adapterPosition) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(float scrollPosition,
|
||||
int currentIndex, int newIndex,
|
||||
@Nullable T currentHolder, @Nullable T newCurrentHolder) {
|
||||
adaptee.onScroll(scrollPosition, currentIndex, newIndex, currentHolder, newCurrentHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ScrollListenerAdapter) {
|
||||
return adaptee.equals(((ScrollListenerAdapter) obj).adaptee);
|
||||
} else {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
libraries/card-library/src/main/res/values/attr.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="DiscreteScrollView">
|
||||
<attr name="dsv_orientation" format="enum">
|
||||
<enum name="horizontal" value="0" />
|
||||
<enum name="vertical" value="1" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
4
libraries/card-library/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="dsv_ex_msg_dont_set_lm">You should not set LayoutManager on DiscreteScrollView.class instance. Library uses a special one. Just don\'t call the method.</string>
|
||||
<string name="dsv_ex_msg_adapter_wrong_recycler">InfiniteScrollAdapter is supposed to work only with DiscreteScrollView</string>
|
||||
</resources>
|
||||
@@ -43,10 +43,10 @@ dependencies {
|
||||
implementation rootProject.ext.dependencies.material
|
||||
annotationProcessor rootProject.ext.dependencies.aroutercompiler
|
||||
implementation rootProject.ext.dependencies.androidxrecyclerview
|
||||
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
implementation rootProject.ext.dependencies.mogomap
|
||||
implementation rootProject.ext.dependencies.mogomapapi
|
||||
implementation rootProject.ext.dependencies.mogomapapi
|
||||
implementation rootProject.ext.dependencies.mogoutils
|
||||
api rootProject.ext.dependencies.mogocommons
|
||||
api rootProject.ext.dependencies.mogoserviceapi
|
||||
@@ -58,6 +58,8 @@ dependencies {
|
||||
api project(":foudations:mogo-commons")
|
||||
api project(':services:mogo-service-api')
|
||||
implementation project(':modules:mogo-module-common')
|
||||
implementation project(":libraries:card-library")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.alibaba.android.arouter.launcher.ARouter;
|
||||
import com.mogo.commons.mvp.MvpFragment;
|
||||
import com.mogo.module.apps.adapter.AppIndicatorAdapter;
|
||||
import com.mogo.module.apps.utils.CardScaleTransformer;
|
||||
import com.mogo.module.apps.utils.LaunchUtils;
|
||||
import com.mogo.module.common.MogoModulePaths;
|
||||
import com.mogo.service.MogoServicePaths;
|
||||
@@ -23,6 +24,8 @@ import com.mogo.service.fragmentmanager.IMogoFragmentManager;
|
||||
import com.mogo.service.module.IMogoModuleProvider;
|
||||
import com.mogo.utils.TipToast;
|
||||
|
||||
import com.yarolegovich.discretescrollview.DiscreteScrollView;
|
||||
import com.yarolegovich.discretescrollview.transform.ScaleTransformer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -34,7 +37,8 @@ import java.util.Map;
|
||||
* 描述
|
||||
*/
|
||||
public class AppNavigatorFragment extends MvpFragment<AppNavigatorView, AppNavigatorPresenter>
|
||||
implements AppNavigatorView {
|
||||
implements AppNavigatorView, DiscreteScrollView.OnItemChangedListener<RecyclerView.ViewHolder>,
|
||||
DiscreteScrollView.ScrollStateChangeListener<RecyclerView.ViewHolder> {
|
||||
|
||||
private View mApps;
|
||||
|
||||
@@ -46,7 +50,7 @@ public class AppNavigatorFragment extends MvpFragment<AppNavigatorView, AppNavig
|
||||
private IMogoModuleProvider mSearchProvider;
|
||||
|
||||
private IMogoAnalytics mMogoAnalytics;
|
||||
private RecyclerView scroller;
|
||||
private DiscreteScrollView scroller;
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
@@ -63,26 +67,31 @@ public class AppNavigatorFragment extends MvpFragment<AppNavigatorView, AppNavig
|
||||
mApps = findViewById(R.id.module_apps_id_apps);
|
||||
|
||||
scroller = findViewById(R.id.module_apps_id_scroller);
|
||||
|
||||
LinearLayoutManager linearLayoutManager =
|
||||
new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
|
||||
|
||||
scroller.setLayoutManager(linearLayoutManager);
|
||||
scroller.setSlideOnFling(true);
|
||||
scroller.addOnItemChangedListener(this);
|
||||
scroller.addScrollStateChangeListener(this);
|
||||
//scroller.setItemTransitionTimeMillis(DiscreteScrollViewOptions.getTransitionTime());
|
||||
scroller.setItemTransformer(new CardScaleTransformer.Builder()
|
||||
.setMinScale(0.84f)
|
||||
.build());
|
||||
//
|
||||
//
|
||||
//LinearLayoutManager linearLayoutManager =
|
||||
// new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
|
||||
//
|
||||
//scroller.setLayoutManager(linearLayoutManager);
|
||||
|
||||
ArrayList<Integer> integers = new ArrayList<>(10);
|
||||
|
||||
integers.add(R.drawable.module_apps_ic_navigation);
|
||||
integers.add(R.drawable.module_apps_ic_interest);
|
||||
integers.add(R.drawable.module_apps_ic_online_car);
|
||||
integers.add(R.drawable.module_apps_ic_news);
|
||||
integers.add(R.drawable.module_apps_ic_tanlu);
|
||||
integers.add(R.drawable.module_apps_ic_media_center);
|
||||
integers.add(R.drawable.module_apps_ic_car_settings);
|
||||
integers.add(R.drawable.module_apps_ic_car_settings);
|
||||
integers.add(R.drawable.module_apps_ic_car_settings);
|
||||
|
||||
AppIndicatorAdapter appIndicatorAdapter = new AppIndicatorAdapter(getContext(), integers);
|
||||
scroller.setAdapter(appIndicatorAdapter);
|
||||
LinearSnapHelper snapHelper = new LinearSnapHelper();
|
||||
snapHelper.attachToRecyclerView(scroller);
|
||||
|
||||
scroller.scrollToPosition(Integer.MAX_VALUE / 2);
|
||||
scroller.scrollToPosition(Integer.MAX_VALUE / 2-1);
|
||||
//mNavigation.setOnClickListener( view -> {
|
||||
// openSearchPanel();
|
||||
// trackNavigatorClickEvent( 1 );
|
||||
@@ -163,4 +172,23 @@ public class AppNavigatorFragment extends MvpFragment<AppNavigatorView, AppNavig
|
||||
mPresenter.onDestroy(getViewLifecycleOwner());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentItemChanged(@Nullable RecyclerView.ViewHolder viewHolder, int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override public void onScrollStart(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override public void onScrollEnd(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(float v, int i, int i1, @Nullable RecyclerView.ViewHolder viewHolder,
|
||||
@Nullable RecyclerView.ViewHolder t1) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,7 @@ public class AppIndicatorAdapter extends RecycleBaseAdapter<Integer> {
|
||||
@Override public void onBindViewHolder(RecycleViewHolder holder, Integer integer) {
|
||||
ImageView ivIndicator = holder.getView(R.id.module_apps_id_app_icon);
|
||||
ivIndicator.setImageResource(integer);
|
||||
holder.setText(R.id.module_apps_id_app_name,names[holder.getLayoutPosition()%5] );
|
||||
}
|
||||
private String[] names=new String[]{"新鲜事","在线车辆","首页","探路","车聊聊"};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.mogo.module.apps.utils;
|
||||
|
||||
import android.view.View;
|
||||
import androidx.annotation.FloatRange;
|
||||
import com.mogo.module.apps.R;
|
||||
import com.yarolegovich.discretescrollview.transform.DiscreteScrollItemTransformer;
|
||||
import com.yarolegovich.discretescrollview.transform.Pivot;
|
||||
import com.yarolegovich.discretescrollview.transform.ScaleTransformer;
|
||||
|
||||
/**
|
||||
* @author zyz
|
||||
* 2020-03-11.
|
||||
*/
|
||||
public class CardScaleTransformer implements DiscreteScrollItemTransformer {
|
||||
|
||||
private Pivot pivotX;
|
||||
private Pivot pivotY;
|
||||
private float minScale;
|
||||
private float maxMinDiff;
|
||||
|
||||
public CardScaleTransformer() {
|
||||
pivotX = Pivot.X.CENTER.create();
|
||||
pivotY = Pivot.Y.BOTTOM.create();
|
||||
minScale = 0.8f;
|
||||
maxMinDiff = 0.2f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformItem(View item, float position) {
|
||||
|
||||
item= item.findViewById(R.id.module_apps_id_app_icon);
|
||||
pivotX.setOn(item);
|
||||
pivotY.setOn(item);
|
||||
float closenessToCenter = 1f - Math.abs(position);
|
||||
float scale = minScale + maxMinDiff * closenessToCenter;
|
||||
item.setScaleX(scale);
|
||||
item.setScaleY(scale);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private CardScaleTransformer transformer;
|
||||
private float maxScale;
|
||||
|
||||
public Builder() {
|
||||
transformer = new CardScaleTransformer();
|
||||
maxScale = 1f;
|
||||
}
|
||||
|
||||
public Builder setMinScale(@FloatRange(from = 0.01) float scale) {
|
||||
transformer.minScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxScale(@FloatRange(from = 0.01) float scale) {
|
||||
maxScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPivotX(Pivot.X pivotX) {
|
||||
return setPivotX(pivotX.create());
|
||||
}
|
||||
|
||||
public Builder setPivotX(Pivot pivot) {
|
||||
assertAxis(pivot, Pivot.AXIS_X);
|
||||
transformer.pivotX = pivot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPivotY(Pivot.Y pivotY) {
|
||||
return setPivotY(pivotY.create());
|
||||
}
|
||||
|
||||
public Builder setPivotY(Pivot pivot) {
|
||||
assertAxis(pivot, Pivot.AXIS_Y);
|
||||
transformer.pivotY = pivot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CardScaleTransformer build() {
|
||||
transformer.maxMinDiff = maxScale - transformer.minScale;
|
||||
return transformer;
|
||||
}
|
||||
|
||||
private void assertAxis(Pivot pivot, @Pivot.Axis int axis) {
|
||||
if (pivot.getAxis() != axis) {
|
||||
throw new IllegalArgumentException("You passed a Pivot for wrong axis.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
BIN
modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_interest.png
Executable file
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
BIN
modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center.png
Normal file → Executable file
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
BIN
modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_news.png
Executable file
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
BIN
modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_tanlu.png
Executable file
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -7,19 +7,40 @@
|
||||
android:paddingBottom="@dimen/module_apps_navigation_icon_paddingBottom"
|
||||
>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<com.yarolegovich.discretescrollview.DiscreteScrollView
|
||||
android:id="@+id/module_apps_id_scroller"
|
||||
android:layout_width="@dimen/dp_480"
|
||||
android:layout_height="@dimen/module_apps_navigation_icon_height"
|
||||
android:layout_width="@dimen/dp_523"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_height="@dimen/module_apps_navigation_icon_container_height"
|
||||
>
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
</com.yarolegovich.discretescrollview.DiscreteScrollView>
|
||||
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_apps_id_apps"
|
||||
android:layout_width="@dimen/module_apps_navigation_icon_width"
|
||||
android:layout_marginTop="@dimen/dp_18"
|
||||
android:layout_height="@dimen/module_apps_navigation_icon_height"
|
||||
app:layout_constraintLeft_toRightOf="@id/module_apps_id_scroller"
|
||||
app:layout_constraintTop_toTopOf="@id/module_apps_id_scroller"
|
||||
android:layout_marginLeft="@dimen/module_apps_navigation_icon_marginLeft"
|
||||
android:src="@drawable/module_apps_ic_apps"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_marginLeft="@dimen/dp_37"
|
||||
app:layout_constraintLeft_toRightOf="@id/module_apps_id_scroller"
|
||||
app:layout_constraintTop_toBottomOf="@id/module_apps_id_apps"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="全部应用"
|
||||
android:maxLines="1"
|
||||
android:layout_marginTop="24px"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textSize="26.25px"
|
||||
/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,26 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_marginLeft="@dimen/dp_30"
|
||||
android:layout_marginRight="@dimen/dp_30"
|
||||
android:layout_width="@dimen/dp_137"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center|bottom"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_apps_id_app_icon"
|
||||
android:layout_width="@dimen/module_apps_navigation_icon_width"
|
||||
android:layout_width="@dimen/dp_112"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_height="@dimen/module_apps_navigation_icon_height" />
|
||||
android:layout_height="@dimen/dp_112" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_apps_id_app_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/module_apps_app_name_marginTop"
|
||||
android:layout_marginTop="@dimen/dp_24"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="@dimen/module_apps_app_name_textSize" />
|
||||
android:textSize="@dimen/dp_26" />
|
||||
</LinearLayout>
|
||||
@@ -5,9 +5,9 @@
|
||||
<dimen name="module_apps_indicator_width">30px</dimen>
|
||||
<dimen name="module_apps_indicator_interval">4px</dimen>
|
||||
<dimen name="module_apps_indicator_marginBottom">103px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_width">120px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_height">120px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_marginLeft">60px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_width">94px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_height">94px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_marginLeft">43px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_paddingBottom">60px</dimen>
|
||||
<dimen name="module_apps_app_name_marginTop">32px</dimen>
|
||||
<dimen name="module_apps_app_name_textSize">32px</dimen>
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
<dimen name="module_apps_page_paddingLeft">220px</dimen>
|
||||
<dimen name="module_apps_page_paddingRight">220px</dimen>
|
||||
<dimen name="module_apps_page_item_verticalSpacing">154px</dimen>
|
||||
<dimen name="module_apps_navigation_icon_container_height">174px</dimen>
|
||||
</resources>
|
||||
@@ -69,7 +69,7 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
|
||||
private IMogoStatusManager mMogoStatusManager;
|
||||
|
||||
private OrientedViewPager mCardsContainer;
|
||||
private VerticalStackTransformer mTransformer;
|
||||
//private VerticalStackTransformer mTransformer;
|
||||
private CardModulesAdapter mCardModulesAdapter;
|
||||
|
||||
private View mHeader;
|
||||
@@ -101,7 +101,7 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
|
||||
protected void initViews() {
|
||||
mCardsContainer = findViewById( R.id.module_main_id_cards_container );
|
||||
mCardsContainer.setOrientation( OrientedViewPager.Orientation.HORIZONTAL );
|
||||
mTransformer = new VerticalStackTransformer( this );
|
||||
//mTransformer = new VerticalStackTransformer( this );
|
||||
mCardsContainer.setOnPageChangeListener( mOnPageChangeListener = new OnPageChangeListenerAdapter() {
|
||||
private boolean mIsLast = true;
|
||||
private boolean mCardFlipStatus = false;
|
||||
@@ -134,7 +134,7 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
|
||||
}
|
||||
} else if ( state == ViewPager.SCROLL_STATE_IDLE ) {
|
||||
mCardFlipStatus = false;
|
||||
mTransformer.resetOffsetScroll();
|
||||
//mTransformer.resetOffsetScroll();
|
||||
}
|
||||
|
||||
int cardSize = mCardModulesAdapter.getCount();
|
||||
@@ -159,7 +159,7 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
|
||||
int positionOffsetPixels ) {
|
||||
super.onPageScrolled( position, positionOffset, positionOffsetPixels );
|
||||
Logger.d( TAG, "pageScrolled : offset --- " + positionOffset );
|
||||
mTransformer.offsetScrollChanged( positionOffset );
|
||||
//mTransformer.offsetScrollChanged( positionOffset );
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -299,7 +299,7 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
|
||||
List< IMogoModuleProvider > providers = mMogoModuleHandler.loadCardsModule();
|
||||
mCardModulesAdapter = new CardModulesAdapter( this, providers );
|
||||
mCardsContainer.setOffscreenPageLimit( providers.size() );
|
||||
mCardsContainer.setPageTransformer( true, mTransformer );
|
||||
//mCardsContainer.setPageTransformer( true, mTransformer );
|
||||
mCardsContainer.setAdapter( mCardModulesAdapter );
|
||||
|
||||
mCardCoverUpBottomLayout.setVisibility( View.VISIBLE );
|
||||
|
||||
BIN
modules/mogo-module-main/src/main/res/drawable-xhdpi/module_apps_bg_card.png
Executable file
|
After Width: | Height: | Size: 60 KiB |
@@ -37,14 +37,18 @@
|
||||
android:layout_marginLeft="@dimen/module_main_card_container_marginLeft"
|
||||
android:layout_marginTop="@dimen/module_main_card_container_marginTop">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:src="@drawable/module_apps_bg_card"
|
||||
android:layout_height="@dimen/dp_797"/>
|
||||
<com.mogo.module.main.cards.OrientedViewPager
|
||||
android:id="@+id/module_main_id_cards_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/module_main_card_container_height"
|
||||
android:layout_marginBottom="@dimen/module_main_card_container_marginBottom"
|
||||
android:layout_marginLeft="@dimen/dp_30"
|
||||
android:layout_marginRight="@dimen/dp_30"
|
||||
android:layout_height="@dimen/dp_600"
|
||||
android:clipToPadding="false"
|
||||
android:overScrollMode="never"
|
||||
android:paddingBottom="@dimen/module_main_card_container_paddingBottom" />
|
||||
android:overScrollMode="never" />
|
||||
|
||||
<!-- 应用入口-->
|
||||
<FrameLayout
|
||||
@@ -57,12 +61,12 @@
|
||||
<FrameLayout
|
||||
android:id="@+id/module_main_id_card_cover_up_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20px"
|
||||
android:layout_marginLeft="15px"
|
||||
android:layout_marginTop="@dimen/module_main_card_cover_up_margin"
|
||||
android:layout_marginRight="15px"
|
||||
android:layout_height="@dimen/dp_26"
|
||||
android:layout_marginTop="@dimen/dp_600"
|
||||
android:layout_marginLeft="@dimen/dp_64"
|
||||
android:layout_marginRight="@dimen/dp_64"
|
||||
android:background="@drawable/module_main_card_cover_up_bottom"
|
||||
android:visibility="gone" />
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ include ':foudations:mogo-utils'
|
||||
include ':services:mogo-service-api'
|
||||
include ':services:mogo-service'
|
||||
include ':libraries:mogo-map'
|
||||
include ':libraries:card-library'
|
||||
include ':foudations:mogo-commons'
|
||||
include ':modules:mogo-module-map'
|
||||
include ':modules:mogo-module-common'
|
||||
|
||||