diff --git a/app/build.gradle b/app/build.gradle index 1c0e75aa78..ad5fcc5f39 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,12 +20,12 @@ android { } multiDexEnabled true - externalNativeBuild { - ndk { - // 设置支持的SO库架构 - abiFilters 'armeabi-v7a' - } - } +// externalNativeBuild { +// ndk { +// // 设置支持的SO库架构 +// abiFilters 'armeabi-v7a' +// } +// } } signingConfigs { release { diff --git a/build.gradle b/build.gradle index fe76aac9ed..0b6d343d97 100644 --- a/build.gradle +++ b/build.gradle @@ -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 } diff --git a/config.gradle b/config.gradle index 5cdac9cd7e..996479e8ca 100644 --- a/config.gradle +++ b/config.gradle @@ -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}", diff --git a/gradle.properties b/gradle.properties index d4e995741d..e0824405ce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file diff --git a/libraries/card-library/.gitignore b/libraries/card-library/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/libraries/card-library/.gitignore @@ -0,0 +1 @@ +/build diff --git a/libraries/card-library/build.gradle b/libraries/card-library/build.gradle new file mode 100644 index 0000000000..de2e3eddb9 --- /dev/null +++ b/libraries/card-library/build.gradle @@ -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 +//} \ No newline at end of file diff --git a/libraries/card-library/gradle.properties b/libraries/card-library/gradle.properties new file mode 100644 index 0000000000..1d49d2e63f --- /dev/null +++ b/libraries/card-library/gradle.properties @@ -0,0 +1,3 @@ +GROUP=com.mogo.ui +POM_ARTIFACT_ID=card-libaray +VERSION_CODE=1 \ No newline at end of file diff --git a/libraries/card-library/proguard-rules.pro b/libraries/card-library/proguard-rules.pro new file mode 100644 index 0000000000..2309852b8f --- /dev/null +++ b/libraries/card-library/proguard-rules.pro @@ -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 *; +#} diff --git a/libraries/card-library/src/main/AndroidManifest.xml b/libraries/card-library/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..f4432f4932 --- /dev/null +++ b/libraries/card-library/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DSVOrientation.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DSVOrientation.java new file mode 100644 index 0000000000..d00779e027 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DSVOrientation.java @@ -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; + } + } + +} diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/Direction.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/Direction.java new file mode 100644 index 0000000000..e4d1c386ce --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/Direction.java @@ -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; + } +} diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DiscreteScrollLayoutManager.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DiscreteScrollLayoutManager.java new file mode 100644 index 0000000000..3cc41b2db5 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DiscreteScrollLayoutManager.java @@ -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 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(); + } +} diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DiscreteScrollView.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DiscreteScrollView.java new file mode 100644 index 0000000000..27973063b8 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/DiscreteScrollView.java @@ -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 scrollStateChangeListeners; + private List 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 { + + 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 { + + void onScroll(float scrollPosition, + int currentPosition, int newPosition, + @Nullable T currentHolder, + @Nullable T newCurrent); + } + + public interface OnItemChangedListener { + /* + * 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); + } +} diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/InfiniteScrollAdapter.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/InfiniteScrollAdapter.java new file mode 100644 index 0000000000..49cdb7ef45 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/InfiniteScrollAdapter.java @@ -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 extends RecyclerView.Adapter + implements DiscreteScrollLayoutManager.InitialPositionProvider { + + private static final int CENTER = Integer.MAX_VALUE / 2; + private static final int RESET_BOUND = 100; + + public static InfiniteScrollAdapter wrap( + @NonNull RecyclerView.Adapter adapter) { + return new InfiniteScrollAdapter<>(adapter); + } + + private RecyclerView.Adapter wrapped; + private DiscreteScrollLayoutManager layoutManager; + + public InfiniteScrollAdapter(@NonNull RecyclerView.Adapter 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); + } + } +} \ No newline at end of file diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/RecyclerViewProxy.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/RecyclerViewProxy.java new file mode 100644 index 0000000000..cbafbf4f6b --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/RecyclerViewProxy.java @@ -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(); + } +} diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/DiscreteScrollItemTransformer.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/DiscreteScrollItemTransformer.java new file mode 100644 index 0000000000..957ae51f41 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/DiscreteScrollItemTransformer.java @@ -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); +} diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/Pivot.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/Pivot.java new file mode 100644 index 0000000000..fc5fcd11d2 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/Pivot.java @@ -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{ + } +} + diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/ScaleTransformer.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/ScaleTransformer.java new file mode 100644 index 0000000000..74a0d26087 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/transform/ScaleTransformer.java @@ -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."); + } + } + } +} diff --git a/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/util/ScrollListenerAdapter.java b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/util/ScrollListenerAdapter.java new file mode 100644 index 0000000000..265ab331f0 --- /dev/null +++ b/libraries/card-library/src/main/java/com/yarolegovich/discretescrollview/util/ScrollListenerAdapter.java @@ -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 implements DiscreteScrollView.ScrollStateChangeListener { + + private DiscreteScrollView.ScrollListener adaptee; + + public ScrollListenerAdapter(@NonNull DiscreteScrollView.ScrollListener 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); + } + } +} diff --git a/libraries/card-library/src/main/res/values/attr.xml b/libraries/card-library/src/main/res/values/attr.xml new file mode 100644 index 0000000000..7310089318 --- /dev/null +++ b/libraries/card-library/src/main/res/values/attr.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/libraries/card-library/src/main/res/values/strings.xml b/libraries/card-library/src/main/res/values/strings.xml new file mode 100644 index 0000000000..4909bfbff7 --- /dev/null +++ b/libraries/card-library/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + You should not set LayoutManager on DiscreteScrollView.class instance. Library uses a special one. Just don\'t call the method. + InfiniteScrollAdapter is supposed to work only with DiscreteScrollView + diff --git a/modules/mogo-module-apps/build.gradle b/modules/mogo-module-apps/build.gradle index 8b87ab74eb..4ac0696a0d 100644 --- a/modules/mogo-module-apps/build.gradle +++ b/modules/mogo-module-apps/build.gradle @@ -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") + } } diff --git a/modules/mogo-module-apps/src/main/java/com/mogo/module/apps/AppNavigatorFragment.java b/modules/mogo-module-apps/src/main/java/com/mogo/module/apps/AppNavigatorFragment.java index 5813143c2b..6cf4635b34 100644 --- a/modules/mogo-module-apps/src/main/java/com/mogo/module/apps/AppNavigatorFragment.java +++ b/modules/mogo-module-apps/src/main/java/com/mogo/module/apps/AppNavigatorFragment.java @@ -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 - implements AppNavigatorView { + implements AppNavigatorView, DiscreteScrollView.OnItemChangedListener, + DiscreteScrollView.ScrollStateChangeListener { private View mApps; @@ -46,7 +50,7 @@ public class AppNavigatorFragment extends MvpFragment 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 { @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[]{"新鲜事","在线车辆","首页","探路","车聊聊"}; } diff --git a/modules/mogo-module-apps/src/main/java/com/mogo/module/apps/utils/CardScaleTransformer.java b/modules/mogo-module-apps/src/main/java/com/mogo/module/apps/utils/CardScaleTransformer.java new file mode 100644 index 0000000000..1bc8021d5d --- /dev/null +++ b/modules/mogo-module-apps/src/main/java/com/mogo/module/apps/utils/CardScaleTransformer.java @@ -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."); + } + } + } +} diff --git a/modules/mogo-module-apps/src/main/res/drawable-ldpi/module_apps_ic_apps.png b/modules/mogo-module-apps/src/main/res/drawable-ldpi/module_apps_ic_apps.png deleted file mode 100755 index c564b8867f..0000000000 Binary files a/modules/mogo-module-apps/src/main/res/drawable-ldpi/module_apps_ic_apps.png and /dev/null differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_apps.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_apps.png index 80ecfda0bd..f1fc6671f2 100755 Binary files a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_apps.png and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_apps.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_chat_icon.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_chat_icon.png new file mode 100755 index 0000000000..9d74b24ea8 Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_chat_icon.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_chat_unchecked.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_chat_unchecked.png new file mode 100755 index 0000000000..9b4d5501dc Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_chat_unchecked.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_interest.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_interest.png new file mode 100755 index 0000000000..5d0d4e1a6d Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_interest.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_interest_unchecked.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_interest_unchecked.png new file mode 100755 index 0000000000..807d603b7b Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_interest_unchecked.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center.png old mode 100644 new mode 100755 index 13aa66438a..1e6247d47a Binary files a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center.png and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center_checked.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center_checked.png new file mode 100755 index 0000000000..ded21b6af2 Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_media_center_checked.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_news.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_news.png new file mode 100755 index 0000000000..b3bc19464e Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_news.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_news_unchecked.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_news_unchecked.png new file mode 100755 index 0000000000..5a64a68660 Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_news_unchecked.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_online_car.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_online_car.png new file mode 100755 index 0000000000..761a11b72a Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_online_car.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_online_car_unchecked.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_online_car_unchecked.png new file mode 100755 index 0000000000..b967fc7071 Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_online_car_unchecked.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_tanlu.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_tanlu.png new file mode 100755 index 0000000000..edd6eaca26 Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_tanlu.png differ diff --git a/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_tanlu_unchecked.png b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_tanlu_unchecked.png new file mode 100755 index 0000000000..ac2a05136b Binary files /dev/null and b/modules/mogo-module-apps/src/main/res/drawable-xhdpi/module_apps_ic_tanlu_unchecked.png differ diff --git a/modules/mogo-module-apps/src/main/res/layout/module_apps_fragment_apps_navigator.xml b/modules/mogo-module-apps/src/main/res/layout/module_apps_fragment_apps_navigator.xml index 2a44855ab2..5c05b17a94 100644 --- a/modules/mogo-module-apps/src/main/res/layout/module_apps_fragment_apps_navigator.xml +++ b/modules/mogo-module-apps/src/main/res/layout/module_apps_fragment_apps_navigator.xml @@ -1,5 +1,5 @@ - - - + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/modules/mogo-module-apps/src/main/res/layout/module_apps_item_app_indicator.xml b/modules/mogo-module-apps/src/main/res/layout/module_apps_item_app_indicator.xml index 8914c979ae..8e02e01c0e 100644 --- a/modules/mogo-module-apps/src/main/res/layout/module_apps_item_app_indicator.xml +++ b/modules/mogo-module-apps/src/main/res/layout/module_apps_item_app_indicator.xml @@ -1,26 +1,24 @@ + android:layout_height="@dimen/dp_112" /> + android:textSize="@dimen/dp_26" /> \ No newline at end of file diff --git a/modules/mogo-module-apps/src/main/res/values-xhdpi/dimens.xml b/modules/mogo-module-apps/src/main/res/values-xhdpi/dimens.xml index ff19f10f5d..8981b061bc 100644 --- a/modules/mogo-module-apps/src/main/res/values-xhdpi/dimens.xml +++ b/modules/mogo-module-apps/src/main/res/values-xhdpi/dimens.xml @@ -5,9 +5,9 @@ 30px 4px 103px - 120px - 120px - 60px + 94px + 94px + 43px 60px 32px 32px diff --git a/modules/mogo-module-apps/src/main/res/values/dimens.xml b/modules/mogo-module-apps/src/main/res/values/dimens.xml index 604c52ddec..f3a1d89594 100644 --- a/modules/mogo-module-apps/src/main/res/values/dimens.xml +++ b/modules/mogo-module-apps/src/main/res/values/dimens.xml @@ -14,4 +14,5 @@ 220px 220px 154px + 174px \ No newline at end of file diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java index 101bfcf81d..8e5e7b14fd 100644 --- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java +++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java @@ -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 ); diff --git a/modules/mogo-module-main/src/main/res/drawable-xhdpi/module_apps_bg_card.png b/modules/mogo-module-main/src/main/res/drawable-xhdpi/module_apps_bg_card.png new file mode 100755 index 0000000000..bb0c8aef1c Binary files /dev/null and b/modules/mogo-module-main/src/main/res/drawable-xhdpi/module_apps_bg_card.png differ diff --git a/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml b/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml index 7173faacc9..c03383b847 100644 --- a/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml +++ b/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml @@ -37,14 +37,18 @@ android:layout_marginLeft="@dimen/module_main_card_container_marginLeft" android:layout_marginTop="@dimen/module_main_card_container_marginTop"> + + android:overScrollMode="never" /> + /> diff --git a/settings.gradle b/settings.gradle index 4747a1bb37..b813673c90 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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'