add carousellayoutmanager

This commit is contained in:
houzh 2024-02-24 22:11:19 +08:00
parent dbf5d80dea
commit bb6d5076f7
10 changed files with 995 additions and 37 deletions

View File

@ -6,22 +6,22 @@ namespace cdroid{
#define MAX_LEVEL 10000
RotateDrawable::RotateState::RotateState()
:DrawableWrapperState(){
mFromDegrees=.0;
mToDegrees=360.0;
mPivotX=mPivotY=0.5;
mPivotXRel=mPivotYRel=true;
mCurrentDegrees=0;
mFromDegrees =.0;
mToDegrees = 360.0;
mPivotX = mPivotY = 0.5;
mPivotXRel = mPivotYRel = true;
mCurrentDegrees = 0;
}
RotateDrawable::RotateState::RotateState(const RotateState& orig)
:DrawableWrapperState(orig){
mFromDegrees=orig.mFromDegrees;
mToDegrees =orig.mToDegrees;
mPivotX =orig.mPivotX;
mPivotY =orig.mPivotY;
mPivotXRel=orig.mPivotXRel;
mPivotYRel=orig.mPivotYRel;
mCurrentDegrees=orig.mCurrentDegrees;
mFromDegrees= orig.mFromDegrees;
mToDegrees = orig.mToDegrees;
mPivotX = orig.mPivotX;
mPivotY = orig.mPivotY;
mPivotXRel= orig.mPivotXRel;
mPivotYRel= orig.mPivotYRel;
mCurrentDegrees = orig.mCurrentDegrees;
}
RotateDrawable*RotateDrawable::RotateState::newDrawable(){
@ -30,12 +30,12 @@ RotateDrawable*RotateDrawable::RotateState::newDrawable(){
//////////////////////////////////////////////////////////////////////////////////////////////
std::shared_ptr<DrawableWrapper::DrawableWrapperState> RotateDrawable::mutateConstantState(){
mState=std::make_shared<RotateState>(*mState);
mState = std::make_shared<RotateState>(*mState);
return mState;
}
RotateDrawable::RotateDrawable(std::shared_ptr<RotateState>state):DrawableWrapper(state){
mState=state;
mState = state;
}
RotateDrawable::RotateDrawable(Drawable*d)
@ -129,22 +129,21 @@ static inline float sdot(float a,float b,float c,float d){
void RotateDrawable::draw(Canvas& canvas) {
Drawable*d = getDrawable();
const Rect& bounds = getBounds();
int w = bounds.width;
int h = bounds.height;
float px = mState->mPivotXRel ? (w * mState->mPivotX) : mState->mPivotX;
float py = mState->mPivotYRel ? (h * mState->mPivotY) : mState->mPivotY;
const int w = bounds.width;
const int h = bounds.height;
const float px = mState->mPivotXRel ? (w * mState->mPivotX) : mState->mPivotX;
const float py = mState->mPivotYRel ? (h * mState->mPivotY) : mState->mPivotY;
#if 1
d->setBounds(bounds);
canvas.save();
canvas.translate(px,py);
canvas.rotate_degrees(mState->mCurrentDegrees);
Rect rd = d->getBounds();
rd.offset(-w/2,-h/2);
d->setBounds(rd);
Rect rect = bounds;
rect.offset(-w/2,-h/2);
d->setBounds(rect);
d->draw(canvas);
rd.offset(w/2,h/2);
d->setBounds(rd);
rect.offset(w/2,h/2);
d->setBounds(rect);
//LOGD("pos=%d,%d/%.f,%.f level=%d degress=%d",bounds.left,bounds.top,px,py,getLevel(),int(mState->mCurrentDegrees));
canvas.restore();
#else

View File

@ -0,0 +1,752 @@
#include <widgetEx/recyclerview/carousellayoutmanager.h>
#include <widgetEx/recyclerview/linearsmoothscroller.h>
//REF[https://github.com/Azoft/CarouselLayoutManager]
namespace cdroid{
CarouselLayoutManager::ItemTransformation::ItemTransformation(float scaleX, float scaleY, float translationX, float translationY) {
mScaleX = scaleX;
mScaleY = scaleY;
mTranslationX = translationX;
mTranslationY = translationY;
}
CarouselLayoutManager::CarouselLayoutManager(int orientation)
:CarouselLayoutManager(orientation, CIRCLE_LAYOUT){
}
CarouselLayoutManager::CarouselLayoutManager(int orientation, bool circleLayout) {
if (HORIZONTAL != orientation && VERTICAL != orientation) {
throw "orientation should be HORIZONTAL or VERTICAL";
}
mOrientation = orientation;
mCircleLayout = circleLayout;
mPendingScrollPosition = INVALID_POSITION;
}
/**
* Change circle layout type
*/
void CarouselLayoutManager::setCircleLayout(bool circleLayout) {
if (mCircleLayout != circleLayout) {
mCircleLayout = circleLayout;
requestLayout();
}
}
/**
* Setup {@link CarouselLayoutManager.PostLayoutListener} for this LayoutManager.
* Its methods will be called for each visible view item after general LayoutManager layout finishes. <br />
* <br />
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
*
* @param postLayoutListener listener for item layout changes. Can be null.
*/
void CarouselLayoutManager::setPostLayoutListener(PostLayoutListener postLayoutListener) {
mViewPostLayout = postLayoutListener;
requestLayout();
}
/**
* Setup maximum visible (layout) items on each side of the center item.
* Basically during scrolling there can be more visible items (+1 item on each side), but in idle state this is the only reached maximum.
*
* @param maxVisibleItems should be great then 0, if bot an {@link IllegalAccessException} will be thrown
*/
void CarouselLayoutManager::setMaxVisibleItems(int maxVisibleItems) {
if (0 > maxVisibleItems) {
throw "maxVisibleItems can't be less then 0";
}
mLayoutHelper->mMaxVisibleItems = maxVisibleItems;
requestLayout();
}
/**
* @return current setup for maximum visible items.
* @see #setMaxVisibleItems(int)
*/
int CarouselLayoutManager::getMaxVisibleItems() const{
return mLayoutHelper->mMaxVisibleItems;
}
RecyclerView::LayoutParams* CarouselLayoutManager::generateDefaultLayoutParams() const{
return new RecyclerView::LayoutParams(ViewGroup::LayoutParams::WRAP_CONTENT, ViewGroup::LayoutParams::WRAP_CONTENT);
}
/**
* @return current layout orientation
* @see #VERTICAL
* @see #HORIZONTAL
*/
int CarouselLayoutManager::getOrientation() const{
return mOrientation;
}
bool CarouselLayoutManager::canScrollHorizontally() const{
return 0 != getChildCount() && HORIZONTAL == mOrientation;
}
bool CarouselLayoutManager::canScrollVertically() const{
return 0 != getChildCount() && VERTICAL == mOrientation;
}
/**
* @return current layout center item
*/
int CarouselLayoutManager::getCenterItemPosition() const{
return mCenterItemPosition;
}
/**
* @param onCenterItemSelectionListener listener that will trigger when ItemSelectionChanges. can't be null
*/
void CarouselLayoutManager::addOnItemSelectionListener(OnCenterItemSelectionListener onCenterItemSelectionListener) {
mOnCenterItemSelectionListeners.push_back(onCenterItemSelectionListener);
}
/**
* @param onCenterItemSelectionListener listener that was previously added by {@link #addOnItemSelectionListener(OnCenterItemSelectionListener)}
*/
void CarouselLayoutManager::removeOnItemSelectionListener(OnCenterItemSelectionListener onCenterItemSelectionListener) {
auto it = std::find(mOnCenterItemSelectionListeners.begin(),
mOnCenterItemSelectionListeners.end(),onCenterItemSelectionListener);
mOnCenterItemSelectionListeners.erase(it);//remove(onCenterItemSelectionListener);
}
void CarouselLayoutManager::scrollToPosition(int position) {
FATAL_IF(position<0,"position can't be less then 0. position is : %d" ,position);
mPendingScrollPosition = position;
requestLayout();
}
class CarouselLayoutManager::CarouselLinearSmoothScroller:public LinearSmoothScroller{
private:
CarouselLayoutManager*mLM;
public:
CarouselLinearSmoothScroller(Context*ctx,CarouselLayoutManager*lm):LinearSmoothScroller(ctx),mLM(lm){
}
int calculateDyToMakeVisible(View* view, int snapPreference) override{
if (!mLM->canScrollVertically()) {
return 0;
}
return mLM->getOffsetForCurrentView(*view);
}
int calculateDxToMakeVisible(View* view, int snapPreference) override{
if (!mLM->canScrollHorizontally()) {
return 0;
}
return mLM->getOffsetForCurrentView(*view);
}
};
void CarouselLayoutManager::smoothScrollToPosition(RecyclerView& recyclerView, RecyclerView::State& state, int position) {
LinearSmoothScroller* linearSmoothScroller = new CarouselLinearSmoothScroller(recyclerView.getContext(),this);
linearSmoothScroller->setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
template<typename T>
T signum(T x) {
return (T(0) < x) - (x < T(0));
}
bool CarouselLayoutManager::computeScrollVectorForPosition(int targetPosition,PointF*pt) {
float directionDistance = getScrollDirection(targetPosition);
//noinspection NumericCastThatLosesPrecision
int direction = (int) -signum(directionDistance);
if(pt)pt->x = pt->y = 0;
if (HORIZONTAL == mOrientation) {
pt->x = direction;
} else {
pt->y = direction;
}
return getChildCount()>0;
}
float CarouselLayoutManager::getScrollDirection(int targetPosition) {
float currentScrollPosition = makeScrollPositionInRange0ToCount(getCurrentScrollPosition(), mItemsCount);
if (mCircleLayout) {
float t1 = currentScrollPosition - targetPosition;
float t2 = std::abs(t1) - mItemsCount;
if (std::abs(t1) > std::abs(t2)) {
return signum(t1) * t2;
} else {
return t1;
}
} else {
return currentScrollPosition - targetPosition;
}
}
int CarouselLayoutManager::scrollVerticallyBy(int dy, RecyclerView::Recycler& recycler, RecyclerView::State& state) {
if (HORIZONTAL == mOrientation) {
return 0;
}
return scrollBy(dy, recycler, state);
}
int CarouselLayoutManager::scrollHorizontallyBy(int dx, RecyclerView::Recycler& recycler, RecyclerView::State& state) {
if (VERTICAL == mOrientation) {
return 0;
}
return scrollBy(dx, recycler, state);
}
/**
* This method is called from {@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)} and
* {@link #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)} to calculate needed scroll that is allowed. <br />
* <br />
* This method may do relayout work.
*
* @param diff distance that we want to scroll by
* @param recycler Recycler to use for fetching potentially cached views for a position
* @param state Transient state of RecyclerView
* @return distance that we actually scrolled by
*/
int CarouselLayoutManager::scrollBy(int diff, RecyclerView::Recycler& recycler, RecyclerView::State& state) {
if ((INT_MIN == mDecoratedChildWidth) || (INT_MIN == mDecoratedChildHeight)) {
return 0;
}
if (0 == getChildCount() || 0 == diff) {
return 0;
}
int resultScroll;
if (mCircleLayout) {
resultScroll = diff;
mLayoutHelper->mScrollOffset += resultScroll;
int maxOffset = getScrollItemSize() * mItemsCount;
while (0 > mLayoutHelper->mScrollOffset) {
mLayoutHelper->mScrollOffset += maxOffset;
}
while (mLayoutHelper->mScrollOffset > maxOffset) {
mLayoutHelper->mScrollOffset -= maxOffset;
}
mLayoutHelper->mScrollOffset -= resultScroll;
} else {
int maxOffset = getMaxScrollOffset();
if (0 > mLayoutHelper->mScrollOffset + diff) {
resultScroll = -mLayoutHelper->mScrollOffset; //to make it 0
} else if (mLayoutHelper->mScrollOffset + diff > maxOffset) {
resultScroll = maxOffset - mLayoutHelper->mScrollOffset; //to make it maxOffset
} else {
resultScroll = diff;
}
}
if (0 != resultScroll) {
mLayoutHelper->mScrollOffset += resultScroll;
fillData(recycler, state);
}
return resultScroll;
}
void CarouselLayoutManager::onMeasure(RecyclerView::Recycler& recycler, RecyclerView::State& state, int widthSpec, int heightSpec) {
mDecoratedChildSizeInvalid = true;
LayoutManager::onMeasure(recycler, state, widthSpec, heightSpec);
}
void CarouselLayoutManager::onAdapterChanged(RecyclerView::Adapter* oldAdapter, RecyclerView::Adapter* newAdapter) {
LayoutManager::onAdapterChanged(oldAdapter, newAdapter);
removeAllViews();
}
void CarouselLayoutManager::onLayoutChildren(RecyclerView::Recycler& recycler, RecyclerView::State& state) {
if (0 == state.getItemCount()) {
removeAndRecycleAllViews(recycler);
selectItemCenterPosition(INVALID_POSITION);
return;
}
detachAndScrapAttachedViews(recycler);
if ((INT_MIN == mDecoratedChildWidth) || mDecoratedChildSizeInvalid) {
std::vector<RecyclerView::ViewHolder*> scrapList = recycler.getScrapList();
bool shouldRecycle;
View* view;
if (scrapList.empty()) {
shouldRecycle = true;
int itemsCount = state.getItemCount();
view = recycler.getViewForPosition(mPendingScrollPosition == INVALID_POSITION ?
0 : std::max(0, std::min(itemsCount - 1, mPendingScrollPosition)) );
addView(view);
} else {
shouldRecycle = false;
view = scrapList.at(0)->itemView;
}
measureChildWithMargins(view, 0, 0);
int decoratedChildWidth = getDecoratedMeasuredWidth(view);
int decoratedChildHeight = getDecoratedMeasuredHeight(view);
if (shouldRecycle) {
detachAndScrapView(view, recycler);
}
if ((INT_MIN != mDecoratedChildWidth) && ((mDecoratedChildWidth != decoratedChildWidth) || (mDecoratedChildHeight != decoratedChildHeight))) {
if ((INVALID_POSITION == mPendingScrollPosition) && (nullptr == mPendingCarouselSavedState)) {
mPendingScrollPosition = mCenterItemPosition;
}
}
mDecoratedChildWidth = decoratedChildWidth;
mDecoratedChildHeight = decoratedChildHeight;
mDecoratedChildSizeInvalid = false;
}
if (INVALID_POSITION != mPendingScrollPosition) {
int itemsCount = state.getItemCount();
mPendingScrollPosition = 0 == itemsCount ? INVALID_POSITION : std::max(0, std::min(itemsCount - 1, mPendingScrollPosition));
}
if (INVALID_POSITION != mPendingScrollPosition) {
mLayoutHelper->mScrollOffset = calculateScrollForSelectingPosition(mPendingScrollPosition, state);
mPendingScrollPosition = INVALID_POSITION;
mPendingCarouselSavedState = nullptr;
} else if (mPendingCarouselSavedState) {
mLayoutHelper->mScrollOffset = calculateScrollForSelectingPosition(mPendingCarouselSavedState->mCenterItemPosition, state);
mPendingCarouselSavedState = nullptr;
} else if (state.didStructureChange() && INVALID_POSITION != mCenterItemPosition) {
mLayoutHelper->mScrollOffset = calculateScrollForSelectingPosition(mCenterItemPosition, state);
}
fillData(recycler, state);
}
int CarouselLayoutManager::calculateScrollForSelectingPosition(int itemPosition, RecyclerView::State& state) {
if (itemPosition == INVALID_POSITION) {
return 0;
}
int fixedItemPosition = itemPosition < state.getItemCount() ? itemPosition : state.getItemCount() - 1;
return fixedItemPosition * (VERTICAL == mOrientation ? mDecoratedChildHeight : mDecoratedChildWidth);
}
void CarouselLayoutManager::fillData(RecyclerView::Recycler& recycler, RecyclerView::State& state) {
float currentScrollPosition = getCurrentScrollPosition();
generateLayoutOrder(currentScrollPosition, state);
detachAndScrapAttachedViews(recycler);
recyclerOldViews(recycler);
int width = getWidthNoPadding();
int height = getHeightNoPadding();
if (VERTICAL == mOrientation) {
fillDataVertical(recycler, width, height);
} else {
fillDataHorizontal(recycler, width, height);
}
recycler.clear();
detectOnItemSelectionChanged(currentScrollPosition, state);
}
void CarouselLayoutManager::detectOnItemSelectionChanged(float currentScrollPosition, RecyclerView::State& state) {
float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, state.getItemCount());
int centerItem = std::round(absCurrentScrollPosition);
if (mCenterItemPosition != centerItem) {
mCenterItemPosition = centerItem;
/*new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
selectItemCenterPosition(centerItem);
}
});*/
}
}
void CarouselLayoutManager::selectItemCenterPosition(int centerItem) {
for (OnCenterItemSelectionListener onCenterItemSelectionListener : mOnCenterItemSelectionListeners) {
onCenterItemSelectionListener(centerItem);
}
}
void CarouselLayoutManager::fillDataVertical(RecyclerView::Recycler& recycler, int width, int height) {
int start = (width - mDecoratedChildWidth) / 2;
int end = start + mDecoratedChildWidth;
int centerViewTop = (height - mDecoratedChildHeight) / 2;
for (int i = 0, count = mLayoutHelper->mLayoutOrder.size(); i < count; ++i) {
LayoutOrder* layoutOrder = mLayoutHelper->mLayoutOrder[i];
int offset = getCardOffsetByPositionDiff(layoutOrder->mItemPositionDiff);
int top = centerViewTop + offset;
int bottom = top + mDecoratedChildHeight;
fillChildItem(start, top, end, bottom, *layoutOrder, recycler, i);
}
}
void CarouselLayoutManager::fillDataHorizontal(RecyclerView::Recycler& recycler, int width, int height) {
int top = (height - mDecoratedChildHeight) / 2;
int bottom = top + mDecoratedChildHeight;
int centerViewStart = (width - mDecoratedChildWidth) / 2;
for (int i = 0, count = mLayoutHelper->mLayoutOrder.size(); i < count; ++i) {
LayoutOrder* layoutOrder = mLayoutHelper->mLayoutOrder[i];
int offset = getCardOffsetByPositionDiff(layoutOrder->mItemPositionDiff);
int start = centerViewStart + offset;
int end = start + mDecoratedChildWidth;
fillChildItem(start, top, end, bottom, *layoutOrder, recycler, i);
}
}
void CarouselLayoutManager::fillChildItem(int start, int top, int end, int bottom,
LayoutOrder& layoutOrder, RecyclerView::Recycler& recycler, int i) {
View* view = bindChild(layoutOrder.mItemAdapterPosition, recycler);
view->setElevation(i);
ItemTransformation* transformation = nullptr;
if (mViewPostLayout) {
transformation = mViewPostLayout(*view, layoutOrder.mItemPositionDiff, mOrientation, layoutOrder.mItemAdapterPosition);
}
if (nullptr == transformation) {
view->layout(start, top, end, bottom);
} else {
view->layout(std::round(start + transformation->mTranslationX), std::round(top + transformation->mTranslationY),
std::round(end + transformation->mTranslationX), std::round(bottom + transformation->mTranslationY));
view->setScaleX(transformation->mScaleX);
view->setScaleY(transformation->mScaleY);
}
}
/**
* @return current scroll position of center item. this value can be in any range if it is cycle layout.
* if this is not, that then it is in [0, {@link #mItemsCount - 1}]
*/
float CarouselLayoutManager::getCurrentScrollPosition() {
int fullScrollSize = getMaxScrollOffset();
if (0 == fullScrollSize) {
return 0;
}
return 1.0f * mLayoutHelper->mScrollOffset / getScrollItemSize();
}
/**
* @return maximum scroll value to fill up all items in layout. Generally this is only needed for non cycle layouts.
*/
int CarouselLayoutManager::getMaxScrollOffset() {
return getScrollItemSize() * (mItemsCount - 1);
}
/**
* Because we can support old Android versions, we should layout our children in specific order to make our center view in the top of layout
* (this item should layout last). So this method will calculate layout order and fill up {@link #mLayoutHelper} object.
* This object will be filled by only needed to layout items. Non visible items will not be there.
*
* @param currentScrollPosition current scroll position this is a value that indicates position of center item
* (if this value is int, then center item is really in the center of the layout, else it is near state).
* Be aware that this value can be in any range is it is cycle layout
* @param state Transient state of RecyclerView
* @see #getCurrentScrollPosition()
*/
void CarouselLayoutManager::generateLayoutOrder(float currentScrollPosition, RecyclerView::State& state) {
mItemsCount = state.getItemCount();
float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, mItemsCount);
int centerItem = std::round(absCurrentScrollPosition);
if (mCircleLayout && 1 < mItemsCount) {
int layoutCount = std::min(mLayoutHelper->mMaxVisibleItems * 2 + 1, mItemsCount);
mLayoutHelper->initLayoutOrder(layoutCount);
int countLayoutHalf = layoutCount / 2;
// before center item
for (int i = 1; i <= countLayoutHalf; ++i) {
int position = static_cast<int>(std::round(absCurrentScrollPosition - i + mItemsCount)) % mItemsCount;
mLayoutHelper->setLayoutOrder(countLayoutHalf - i, position, centerItem - absCurrentScrollPosition - i);
}
// after center item
for (int i = layoutCount - 1; i >= countLayoutHalf + 1; --i) {
int position = static_cast<int>(std::round(absCurrentScrollPosition - i + layoutCount)) % mItemsCount;
mLayoutHelper->setLayoutOrder(i - 1, position, centerItem - absCurrentScrollPosition + layoutCount - i);
}
mLayoutHelper->setLayoutOrder(layoutCount - 1, centerItem, centerItem - absCurrentScrollPosition);
} else {
int firstVisible = std::max(centerItem - mLayoutHelper->mMaxVisibleItems, 0);
int lastVisible = std::min(centerItem + mLayoutHelper->mMaxVisibleItems, mItemsCount - 1);
int layoutCount = lastVisible - firstVisible + 1;
mLayoutHelper->initLayoutOrder(layoutCount);
for (int i = firstVisible; i <= lastVisible; ++i) {
if (i == centerItem) {
mLayoutHelper->setLayoutOrder(layoutCount - 1, i, i - absCurrentScrollPosition);
} else if (i < centerItem) {
mLayoutHelper->setLayoutOrder(i - firstVisible, i, i - absCurrentScrollPosition);
} else {
mLayoutHelper->setLayoutOrder(layoutCount - (i - centerItem) - 1, i, i - absCurrentScrollPosition);
}
}
}
}
int CarouselLayoutManager::getWidthNoPadding() {
return getWidth() - getPaddingStart() - getPaddingEnd();
}
int CarouselLayoutManager::getHeightNoPadding() {
return getHeight() - getPaddingEnd() - getPaddingStart();
}
View* CarouselLayoutManager::bindChild(int position, RecyclerView::Recycler& recycler) {
View* view = recycler.getViewForPosition(position);
addView(view);
measureChildWithMargins(view, 0, 0);
return view;
}
void CarouselLayoutManager::recyclerOldViews(RecyclerView::Recycler& recycler) {
auto scrapList = recycler.getScrapList();
for (RecyclerView::ViewHolder* viewHolder : scrapList) {
int adapterPosition = viewHolder->getAdapterPosition();
bool found = false;
for (LayoutOrder* layoutOrder : mLayoutHelper->mLayoutOrder) {
if (layoutOrder->mItemAdapterPosition == adapterPosition) {
found = true;
break;
}
}
if (!found) {
recycler.recycleView(viewHolder->itemView);
}
}
}
/**
* Called during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)} to calculate item offset from layout center line. <br />
* <br />
* Returns {@link #convertItemPositionDiffToSmoothPositionDiff(float)} * (size off area above center item when it is on the center). <br />
* Sign is: plus if this item is bellow center line, minus if not<br />
* <br />
* ----- - area above it<br />
* ||||| - center item<br />
* ----- - area bellow it (it has the same size as are above center item)<br />
*
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
* if this is 1 then this item is bellow the layout center line in the full item size distance.
* @return offset in scroll px coordinates.
*/
int CarouselLayoutManager::getCardOffsetByPositionDiff(float itemPositionDiff) {
double smoothPosition = convertItemPositionDiffToSmoothPositionDiff(itemPositionDiff);
int dimenDiff;
if (VERTICAL == mOrientation) {
dimenDiff = (getHeightNoPadding() - mDecoratedChildHeight) / 2;
} else {
dimenDiff = (getWidthNoPadding() - mDecoratedChildWidth) / 2;
}
//noinspection NumericCastThatLosesPrecision
return (int) std::round(signum(itemPositionDiff) * dimenDiff * smoothPosition);
}
/**
* Called during {@link #getCardOffsetByPositionDiff(float)} for better item movement. <br/>
* Current implementation speed up items that are far from layout center line and slow down items that are close to this line.
* This code is full of maths. If you want to make items move in a different way, probably you should override this method.<br />
* Please see code comments for better explanations.
*
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
* if this is 1 then this item is bellow the layout center line in the full item size distance.
* @return smooth position offset. needed for scroll calculation and better user experience.
* @see #getCardOffsetByPositionDiff(float)
*/
double CarouselLayoutManager::convertItemPositionDiffToSmoothPositionDiff(float itemPositionDiff) {
// generally item moves the same way above center and bellow it. So we don't care about diff sign.
float absIemPositionDiff = std::abs(itemPositionDiff);
// we detect if this item is close for center or not. We use (1 / maxVisibleItem) ^ (1/3) as close definer.
if (absIemPositionDiff > std::pow(1.0f / mLayoutHelper->mMaxVisibleItems, 1.0f / 3)) {
// this item is far from center line, so we should make it move like square root function
return std::pow(absIemPositionDiff / mLayoutHelper->mMaxVisibleItems, 1 / 2.0f);
} else {
// this item is close from center line. we should slow it down and don't make it speed up very quick.
// so square function in range of [0, (1/maxVisible)^(1/3)] is quite good in it;
return std::pow(absIemPositionDiff, 2.0f);
}
}
/**
* @return full item size
*/
int CarouselLayoutManager::getScrollItemSize() {
if (VERTICAL == mOrientation) {
return mDecoratedChildHeight;
} else {
return mDecoratedChildWidth;
}
}
Parcelable* CarouselLayoutManager::onSaveInstanceState() {
if (mPendingCarouselSavedState) {
return new CarouselSavedState(*mPendingCarouselSavedState);
}
CarouselSavedState* savedState = new CarouselSavedState(LayoutManager::onSaveInstanceState());
savedState->mCenterItemPosition = mCenterItemPosition;
return savedState;
}
void CarouselLayoutManager::onRestoreInstanceState(Parcelable& state) {
if (dynamic_cast<CarouselSavedState*>(&state)) {
mPendingCarouselSavedState = (CarouselSavedState*) &state;
LayoutManager::onRestoreInstanceState(*mPendingCarouselSavedState->mSuperState);
} else {
LayoutManager::onRestoreInstanceState(state);
}
}
/**
* @return Scroll offset from nearest item from center
*/
int CarouselLayoutManager::getOffsetCenterView() {
return std::round(getCurrentScrollPosition()) * getScrollItemSize() - mLayoutHelper->mScrollOffset;
}
int CarouselLayoutManager::getOffsetForCurrentView(View& view) {
int targetPosition = getPosition(&view);
float directionDistance = getScrollDirection(targetPosition);
return std::round(directionDistance * getScrollItemSize());
}
/**
* Helper method that make scroll in range of [0, count). Generally this method is needed only for cycle layout.
*
* @param currentScrollPosition any scroll position range.
* @param count adapter items count
* @return good scroll position in range of [0, count)
*/
float CarouselLayoutManager::makeScrollPositionInRange0ToCount(float currentScrollPosition, int count) {
float absCurrentScrollPosition = currentScrollPosition;
while (0 > absCurrentScrollPosition) {
absCurrentScrollPosition += count;
}
while (std::round(absCurrentScrollPosition) >= count) {
absCurrentScrollPosition -= count;
}
return absCurrentScrollPosition;
}
#if 0
public abstract static class PostLayoutListener {
public ItemTransformation transformChild(
@NonNull View child,
float itemPositionToCenterDiff,
int orientation,
int itemPositionInAdapter
) {
return transformChild(child, itemPositionToCenterDiff, orientation);
}
public ItemTransformation transformChild(
@NonNull View child,
float itemPositionToCenterDiff,
int orientation
) {
throw new IllegalStateException("at least one transformChild should be implemented");
}
}
#endif
CarouselLayoutManager::LayoutHelper::LayoutHelper(int maxVisibleItems) {
mMaxVisibleItems = maxVisibleItems;
}
/**
* Called before any fill calls. Needed to recycle old items and init new array list. Generally this list is an array an it is reused.
*
* @param layoutCount items count that will be layout
*/
void CarouselLayoutManager::LayoutHelper::initLayoutOrder(int layoutCount) {
if (mLayoutOrder.size() != layoutCount) {
if (mLayoutOrder.size()) {
recycleItems(mLayoutOrder);
}
mLayoutOrder.resize(layoutCount);//mLayoutOrder= new LayoutOrder[layoutCount];
fillLayoutOrder();
}
}
void CarouselLayoutManager::LayoutHelper::setLayoutOrder(int arrayPosition, int itemAdapterPosition, float itemPositionDiff) {
LayoutOrder* item = mLayoutOrder[arrayPosition];
item->mItemAdapterPosition = itemAdapterPosition;
item->mItemPositionDiff = itemPositionDiff;
}
/**
* Checks is this screen Layout has this adapterPosition view in layout
*
* @param adapterPosition adapter position of item for future data filling logic
* @return true is adapterItem is in layout
*/
bool CarouselLayoutManager::LayoutHelper::hasAdapterPosition(int adapterPosition) {
if (mLayoutOrder.size()) {
for (LayoutOrder* layoutOrder : mLayoutOrder) {
if (layoutOrder->mItemAdapterPosition == adapterPosition) {
return true;
}
}
}
return false;
}
void CarouselLayoutManager::LayoutHelper::recycleItems(const std::vector<LayoutOrder*>&layoutOrders) {
for (LayoutOrder* layoutOrder : layoutOrders) {
//noinspection ObjectAllocationInLoop
mReusedItems.push_back(layoutOrder);//add(new WeakReference<>(layoutOrder));
}
}
void CarouselLayoutManager::LayoutHelper::fillLayoutOrder() {
for (int i = 0, length = mLayoutOrder.size(); i < length; ++i) {
if (nullptr == mLayoutOrder[i]) {
mLayoutOrder[i] = createLayoutOrder();
}
}
}
CarouselLayoutManager::LayoutOrder* CarouselLayoutManager::LayoutHelper::createLayoutOrder() {
/*Iterator<LayoutOrder*> iterator = mReusedItems.iterator();
while (iterator.hasNext()) {
WeakReference<LayoutOrder> layoutOrderWeakReference = iterator.next();
LayoutOrder* layoutOrder = layoutOrderWeakReference.get();
iterator.remove();
if (nullptr != layoutOrder) {
return layoutOrder;
}
}*/
return new LayoutOrder();
}
CarouselLayoutManager::CarouselSavedState::CarouselSavedState(Parcelable* superState) {
//mSuperState = superState;
}
CarouselLayoutManager::CarouselSavedState::CarouselSavedState(Parcel& in) {
//mSuperState = in.readParcelable(Parcelable.class.getClassLoader());
mCenterItemPosition = in.readInt();
}
CarouselLayoutManager::CarouselSavedState::CarouselSavedState(CarouselSavedState& other) {
//mSuperState = other.mSuperState;
mCenterItemPosition = other.mCenterItemPosition;
}
int CarouselLayoutManager::CarouselSavedState::describeContents() {
return 0;
}
void CarouselLayoutManager::CarouselSavedState::writeToParcel(Parcel& parcel, int i) {
//parcel.writeParcelable(mSuperState, i);
parcel.writeInt(mCenterItemPosition);
}
}/*endof namespace*/

View File

@ -0,0 +1,208 @@
#ifndef __CAROUSEL_LAYOUTMANAGER_H__
#define __CAROUSEL_LAYOUTMANAGER_H__
#include <widgetEx/recyclerview/recyclerview.h>
#include <widgetEx/recyclerview/orientationhelper.h>
namespace cdroid{
class CarouselLayoutManager :public RecyclerView::LayoutManager{
public:
static constexpr int HORIZONTAL = OrientationHelper::HORIZONTAL;
static constexpr int VERTICAL = OrientationHelper::VERTICAL;
static constexpr int INVALID_POSITION = -1;
static constexpr int MAX_VISIBLE_ITEMS = 3;
typedef CallbackBase<void,int>OnCenterItemSelectionListener;
struct ItemTransformation {
float mScaleX;
float mScaleY;
float mTranslationX;
float mTranslationY;
ItemTransformation(float scaleX, float scaleY, float translationX, float translationY);
};
/**
* Called after child layout finished. Generally you can do any translation and scaling work here.
*
* @param child view that was layout
* @param itemPositionToCenterDiff view center line difference to layout center.
* if > 0 then this item is bellow layout center line, else if not
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
* @param itemPositionInAdapter item position inside adapter for this layout pass
*/
typedef CallbackBase<ItemTransformation*,View&,float,int,int>PostLayoutListener;
private:
static constexpr bool CIRCLE_LAYOUT = false;
struct LayoutOrder;
class LayoutHelper;
class CarouselLinearSmoothScroller;
friend CarouselLinearSmoothScroller;
int mDecoratedChildWidth;
int mDecoratedChildHeight;
int mOrientation;
int mPendingScrollPosition;
bool mDecoratedChildSizeInvalid;
bool mCircleLayout;
LayoutHelper* mLayoutHelper;// = new LayoutHelper(MAX_VISIBLE_ITEMS);
PostLayoutListener mViewPostLayout;
std::vector<OnCenterItemSelectionListener> mOnCenterItemSelectionListeners;
int mCenterItemPosition = INVALID_POSITION;
int mItemsCount;
private:
int calculateScrollForSelectingPosition(int itemPosition, RecyclerView::State& state);
void fillData(RecyclerView::Recycler& recycler, RecyclerView::State& state);
void detectOnItemSelectionChanged(float currentScrollPosition, RecyclerView::State& state);
void selectItemCenterPosition(int centerItem);
void fillDataVertical(RecyclerView::Recycler& recycler, int width, int height);
void fillDataHorizontal(RecyclerView::Recycler& recycler, int width, int height);
void fillChildItem(int start, int top, int end, int bottom, LayoutOrder& layoutOrder, RecyclerView::Recycler& recycler, int i);
/**
* @return current scroll position of center item. this value can be in any range if it is cycle layout.
* if this is not, that then it is in [0, {@link #mItemsCount - 1}]
*/
float getCurrentScrollPosition();
/**
* @return maximum scroll value to fill up all items in layout. Generally this is only needed for non cycle layouts.
*/
int getMaxScrollOffset();
float getScrollDirection(int targetPosition);
void generateLayoutOrder(float currentScrollPosition,RecyclerView::State& state);
View* bindChild(int position, RecyclerView::Recycler& recycler);
void recyclerOldViews(RecyclerView::Recycler& recycler);
static float makeScrollPositionInRange0ToCount(float currentScrollPosition, int count);
protected:
class CarouselSavedState;
CarouselSavedState* mPendingCarouselSavedState;
int scrollBy(int diff,RecyclerView::Recycler& recycler, RecyclerView::State& state);
int getCardOffsetByPositionDiff(float itemPositionDiff);
double convertItemPositionDiffToSmoothPositionDiff(float itemPositionDiff);
int getScrollItemSize();
int getOffsetCenterView();
int getOffsetForCurrentView(View& view);
public:
/**
* @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
*/
CarouselLayoutManager(int orientation);
CarouselLayoutManager(int orientation, bool circleLayout);
/**
* Change circle layout type
*/
void setCircleLayout(bool circleLayout);
/**
* Setup {@link CarouselLayoutManager::PostLayoutListener} for this LayoutManager.
* Its methods will be called for each visible view item after general LayoutManager layout finishes. <br />
* <br />
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
*
* @param postLayoutListener listener for item layout changes. Can be null.
*/
void setPostLayoutListener(PostLayoutListener postLayoutListener);
/**
* Setup maximum visible (layout) items on each side of the center item.
* Basically during scrolling there can be more visible items (+1 item on each side), but in idle state this is the only reached maximum.
*
* @param maxVisibleItems should be great then 0, if bot an {@link IllegalAccessException} will be thrown
*/
void setMaxVisibleItems(int maxVisibleItems);
/**
* @return current setup for maximum visible items.
* @see #setMaxVisibleItems(int)
*/
int getMaxVisibleItems()const;
RecyclerView::LayoutParams* generateDefaultLayoutParams()const override;
/**
* @return current layout orientation
* @see #VERTICAL
* @see #HORIZONTAL
*/
int getOrientation()const;
bool canScrollHorizontally()const override;
bool canScrollVertically()const override;
/**
* @return current layout center item
*/
int getCenterItemPosition()const;
/**
* @param onCenterItemSelectionListener listener that will trigger when ItemSelectionChanges. can't be null
*/
void addOnItemSelectionListener(OnCenterItemSelectionListener onCenterItemSelectionListener);
/**
* @param onCenterItemSelectionListener listener that was previously added by {@link #addOnItemSelectionListener(OnCenterItemSelectionListener)}
*/
void removeOnItemSelectionListener(OnCenterItemSelectionListener onCenterItemSelectionListener);
void scrollToPosition(int position) override;
void smoothScrollToPosition(RecyclerView& recyclerView, RecyclerView::State& state, int position)override;
bool computeScrollVectorForPosition(int targetPosition,PointF*)override;
int scrollVerticallyBy(int dy, RecyclerView::Recycler& recycler, RecyclerView::State& state)override;
int scrollHorizontallyBy(int dx, RecyclerView::Recycler& recycler, RecyclerView::State& state)override;
void onMeasure(RecyclerView::Recycler& recycler, RecyclerView::State& state, int widthSpec, int heightSpec)override;
void onAdapterChanged(RecyclerView::Adapter* oldAdapter, RecyclerView::Adapter* newAdapter)override;
void onLayoutChildren(RecyclerView::Recycler& recycler,RecyclerView::State& state)override;
int getWidthNoPadding();
int getHeightNoPadding();
Parcelable* onSaveInstanceState()override;
void onRestoreInstanceState(Parcelable& state)override;
/**
* This interface methods will be called for each visible view item after general LayoutManager layout finishes. <br />
* <br />
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
*/
};/*end carousellayoutmanager*/
struct CarouselLayoutManager::LayoutOrder {
int mItemAdapterPosition;
float mItemPositionDiff;
friend LayoutHelper;
};
class CarouselLayoutManager::LayoutHelper {
private :
friend CarouselLayoutManager;
int mMaxVisibleItems;
int mScrollOffset;
std::vector<LayoutOrder*> mLayoutOrder;
std::vector<LayoutOrder*> mReusedItems;
private:
void recycleItems(const std::vector<LayoutOrder*>& layoutOrders);
void fillLayoutOrder();
LayoutOrder* createLayoutOrder();
public:
LayoutHelper(int maxVisibleItems);
void initLayoutOrder(int layoutCount);
void setLayoutOrder(int arrayPosition, int itemAdapterPosition, float itemPositionDiff);
bool hasAdapterPosition(int adapterPosition);
};
class CarouselLayoutManager::CarouselSavedState :public Parcelable {
private:
friend CarouselLayoutManager;
Parcelable* mSuperState;
int mCenterItemPosition;
CarouselSavedState(Parcel& in);
protected:
CarouselSavedState(Parcelable* superState);
CarouselSavedState(CarouselSavedState& other);
public:
int describeContents();
void writeToParcel(Parcel& parcel, int i);
};
}/*endof namespace*/
#endif/*__CAROUSEL_LAYOUTMANAGER_H__*/

View File

@ -133,7 +133,7 @@ void ChildHelper::attachViewToParent(View* child, int index, ViewGroup::LayoutPa
LOGD_IF(_DEBUG,"attach view to parent index:%d off:%d h:%d ",index,offset,hidden);
}
int ChildHelper::getChildCount(){
int ChildHelper::getChildCount()const{
return mCallback.getChildCount() - mHiddenViews.size();
}

View File

@ -37,7 +37,7 @@ public:
View* findHiddenNonRemovedView(int position);
void attachViewToParent(View* child, int index, ViewGroup::LayoutParams* layoutParams,
bool hidden);
int getChildCount();
int getChildCount()const;
int getUnfilteredChildCount();
View* getUnfilteredChildAt(int index);
void detachViewFromParent(int index);

View File

@ -35,8 +35,8 @@ public:
int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
snapPreference);
int calculateDyToMakeVisible(View* view, int snapPreference);
int calculateDxToMakeVisible(View* view, int snapPreference);
virtual int calculateDyToMakeVisible(View* view, int snapPreference);
virtual int calculateDxToMakeVisible(View* view, int snapPreference);
};
}/*endof namespace*/
#endif/*__LINEAR_SMOOTH_SCROLLER_H__*/

View File

@ -317,14 +317,13 @@ void RecyclerView::initChildrenHelper() {
return getChildViewHolderInt(view);
};
cbk.attachViewToParent=[this](View* child, int index,
ViewGroup::LayoutParams* layoutParams) {
cbk.attachViewToParent=[this](View* child, int index, ViewGroup::LayoutParams* layoutParams) {
ViewHolder* vh = getChildViewHolderInt(child);
if (vh != nullptr) {
if (!vh->isTmpDetached() && !vh->shouldIgnore()) {
LOGE("Called attach on a child which is not detached: %p",vh);
}
LOGD("reAttach %p",vh);
LOGD_IF(_DEBUG,"reAttach %p",vh);
vh->clearTmpDetachFlag();
}
attachViewToParent(child, index, layoutParams);
@ -338,7 +337,7 @@ void RecyclerView::initChildrenHelper() {
if (vh->isTmpDetached() && !vh->shouldIgnore()) {
LOGE("called detach on an already detached child %p",vh);
}
LOGD("tmpDetach =%p",vh);
LOGD_IF(_DEBUG,"tmpDetach =%p",vh);
vh->addFlags(ViewHolder::FLAG_TMP_DETACHED);
}
}
@ -1880,7 +1879,7 @@ bool RecyclerView::onTouchEvent(MotionEvent& e) {
mVelocityTracker->computeCurrentVelocity(1000, mMaxFlingVelocity);
const float xvel = bCanScrollHorizontally ? -mVelocityTracker->getXVelocity(mScrollPointerId) : 0;
const float yvel = bCanScrollVertically ? -mVelocityTracker->getYVelocity(mScrollPointerId) : 0;
LOGD("xvel=%.2f yvel=%.2f",xvel,yvel);
LOGV("xvel=%.2f yvel=%.2f",xvel,yvel);
if (!(((xvel != 0) || (yvel != 0)) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
@ -5184,7 +5183,7 @@ void RecyclerView::LayoutManager::removeAndRecycleViewAt(int index,Recycler& rec
recycler.recycleView(view);
}
int RecyclerView::LayoutManager::getChildCount() {
int RecyclerView::LayoutManager::getChildCount() const{
return mChildHelper ? mChildHelper->getChildCount() : 0;
}

View File

@ -704,7 +704,7 @@ public:
void detachAndScrapViewAt(int index,Recycler& recycler);
void removeAndRecycleView(View* child, Recycler& recycler);
void removeAndRecycleViewAt(int index,Recycler& recycler);
int getChildCount();
int getChildCount()const;
View* getChildAt(int index);
int getWidthMode();
int getHeightMode();

View File

@ -1736,7 +1736,7 @@ bool StaggeredGridLayoutManager::checkLayoutParams(const RecyclerView::LayoutPar
return dynamic_cast<const LayoutParams*>(lp);
}
int StaggeredGridLayoutManager::getOrientation() {
int StaggeredGridLayoutManager::getOrientation() const{
return mOrientation;
}

View File

@ -169,7 +169,7 @@ public:
RecyclerView::LayoutParams* generateLayoutParams(Context* c,const AttributeSet& attrs)const override;
RecyclerView::LayoutParams* generateLayoutParams(const ViewGroup::LayoutParams& lp)const override;
bool checkLayoutParams(const RecyclerView::LayoutParams* lp)const override;
int getOrientation();
int getOrientation()const;
View* onFocusSearchFailed(View* focused, int direction, RecyclerView::Recycler& recycler, RecyclerView::State& state);
};/*StaggeredGridLayoutManager*/