adviewpager2

This commit is contained in:
houzh 2024-02-05 16:23:44 +00:00
parent 021ec5c3d9
commit 73cf7c2015
2 changed files with 1223 additions and 0 deletions

944
src/gui/widgetEx/viewpager2.cc Executable file
View File

@ -0,0 +1,944 @@
#include <widgetEx/viewpager2.h>
#include <widgetEx/recyclerview/pagersnaphelper.h>
#include <widgetEx/scrolleventadapter.h>
#include <widgetEx/fakedrag.h>
#include <widgetEx/compositeonpagechangecallback.h>
namespace cdroid{
class PageTransformerAdapter:public ViewPager2::OnPageChangeCallback {
private:
LinearLayoutManager* mLayoutManager;
ViewPager2::PageTransformer* mPageTransformer;
public:
PageTransformerAdapter(LinearLayoutManager* layoutManager) {
mLayoutManager = layoutManager;
onPageScrolled = std::bind(&PageTransformerAdapter::doPageScrolled,this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
}
ViewPager2::PageTransformer* getPageTransformer() {
return mPageTransformer;
}
void setPageTransformer(ViewPager2::PageTransformer* transformer) {
mPageTransformer = transformer;
}
void doPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mPageTransformer != nullptr) {
const float transformOffset = -positionOffset;
for(int i = 0; i < mLayoutManager->getChildCount(); ++i) {
View* view = mLayoutManager->getChildAt(i);
FATAL_IF(view==nullptr,"LayoutManager returned a null child at pos %d/%d while transforming pages", i, mLayoutManager->getChildCount());
const int currPos = mLayoutManager->getPosition(view);
const float viewOffset = transformOffset + (float)(currPos - position);
mPageTransformer->transformPage(*view, viewOffset);
}
}
}
};
ViewPager2::ViewPager2(Context* context,const AttributeSet& attrs)
:ViewGroup(context, attrs){
initialize(context, attrs);
}
void ViewPager2::initialize(Context* context,const AttributeSet& attrs) {
/*mAccessibilityProvider = sFeatureEnhancedA11yEnabled
? new PageAwareAccessibilityProvider()
: new BasicAccessibilityProvider();*/
mRecyclerView = new RecyclerViewImpl(context,attrs);
mRecyclerView->mVP =this;
mRecyclerView->setId(View::generateViewId());
mRecyclerView->setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
mLayoutManager = new LinearLayoutManagerImpl(context,this);
mRecyclerView->setLayoutManager(mLayoutManager);
mRecyclerView->setScrollingTouchSlop(RecyclerView::TOUCH_SLOP_PAGING);
setOrientation(context, attrs);
mRecyclerView->setLayoutParams(
new ViewGroup::LayoutParams(LayoutParams::MATCH_PARENT, LayoutParams::MATCH_PARENT));
RecyclerView::OnChildAttachStateChangeListener ls;
ls.onChildViewAttachedToWindow=[](View&view){
RecyclerView::LayoutParams* layoutParams = (RecyclerView::LayoutParams*) view.getLayoutParams();
if (layoutParams->width != LayoutParams::MATCH_PARENT
|| layoutParams->height != LayoutParams::MATCH_PARENT) {
FATAL("Pages must fill the whole ViewPager2 (use match_parent)");
}
};
ls.onChildViewDetachedFromWindow=[](View&){
// nothing
};
mRecyclerView->addOnChildAttachStateChangeListener(ls);//enforceChildFillListener());
// Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
// attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
mScrollEventAdapter = new ScrollEventAdapter(this);
// Create FakeDrag before attaching PagerSnapHelper, same reason as above
mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
mPagerSnapHelper = new PagerSnapHelperImpl();
mPagerSnapHelper->attachToRecyclerView(mRecyclerView);
// Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
// don't want to respond on the events sent out during the attach process
mRecyclerView->addOnScrollListener(*mScrollEventAdapter);
mPageChangeEventDispatcher = new CompositeOnPageChangeCallback(3);
mScrollEventAdapter->setOnPageChangeCallback(*mPageChangeEventDispatcher);
// Callback that updates mCurrentItem after swipes. Also triggered in other cases, but in
// all those cases mCurrentItem will only be overwritten with the same value.
OnPageChangeCallback currentItemUpdater;// = new OnPageChangeCallback() {
currentItemUpdater.onPageSelected=[this](int position) {
if (mCurrentItem != position) {
mCurrentItem = position;
//mAccessibilityProvider.onSetNewCurrentItem();
}
};
currentItemUpdater.onPageScrollStateChanged=[this](int newState) {
if (newState == SCROLL_STATE_IDLE) {
updateCurrentItem();
}
};
// Prevents focus from remaining on a no-longer visible page
OnPageChangeCallback focusClearer;// = new OnPageChangeCallback() {
focusClearer.onPageSelected=[this](int position) {
clearFocus();
if (hasFocus()) { // if clear focus did not succeed
mRecyclerView->requestFocus(View::FOCUS_FORWARD);
}
};
// Add currentItemUpdater before mExternalPageChangeCallbacks, because we need to update
// internal state first
mPageChangeEventDispatcher->addOnPageChangeCallback(currentItemUpdater);
mPageChangeEventDispatcher->addOnPageChangeCallback(focusClearer);
// Allow a11y to register its listeners after currentItemUpdater (so it has the
// right data). TODO: replace ordering comments with a test.
//mAccessibilityProvider->onInitialize(mPageChangeEventDispatcher, mRecyclerView);
mPageChangeEventDispatcher->addOnPageChangeCallback(*mExternalPageChangeCallbacks);
// Add mPageTransformerAdapter after mExternalPageChangeCallbacks, because page transform
// events must be fired after scroll events
mPageTransformerAdapter = new PageTransformerAdapter(mLayoutManager);
mPageChangeEventDispatcher->addOnPageChangeCallback(*mPageTransformerAdapter);
attachViewToParent(mRecyclerView, 0, mRecyclerView->getLayoutParams());
}
#if 0
RecyclerView::OnChildAttachStateChangeListener ViewPager2::enforceChildFillListener() {
return nullptr;/*new RecyclerView::OnChildAttachStateChangeListener() {
public void onChildViewAttachedToWindow(View* view) {
RecyclerView::LayoutParams* layoutParams = (RecyclerView::LayoutParams*) view->getLayoutParams();
if (layoutParams->width != LayoutParams::MATCH_PARENT
|| layoutParams->height != LayoutParams::MATCH_PARENT) {
FATAL("Pages must fill the whole ViewPager2 (use match_parent)");
}
}
public void onChildViewDetachedFromWindow(View* view) {
// nothing
}
};*/
}
CharSequence ViewPager2::getAccessibilityClassName() {
if (mAccessibilityProvider.handlesGetAccessibilityClassName()) {
return mAccessibilityProvider.onGetAccessibilityClassName();
}
return super.getAccessibilityClassName();
}
#endif
void ViewPager2::setOrientation(Context* context,const AttributeSet& attrs) {
setOrientation(ORIENTATION_HORIZONTAL);
//a.getInt(R.styleable.ViewPager2_android_orientation, ORIENTATION_HORIZONTAL));
}
#if 0
Parcelable* ViewPager2::onSaveInstanceState() {
Parcelable* superState = ViewGroup::onSaveInstanceState();
SavedState* ss = new SavedState(*superState);
ss->mRecyclerViewId = mRecyclerView->getId();
ss->mCurrentItem = mPendingCurrentItem == RecyclerView::NO_POSITION ? mCurrentItem : mPendingCurrentItem;
if (mPendingAdapterState != nullptr) {
ss->mAdapterState = mPendingAdapterState;
} else {
RecyclerView::Adapter*adapter = mRecyclerView->getAdapter();
/*if (adapter instanceof StatefulAdapter) {
ss->mAdapterState = ((StatefulAdapter) adapter).saveState();
}*/
}
return ss;
}
void ViewPager2::onRestoreInstanceState(Parcelable& state) {
if ((dynamic_cast<SavedState*>(&state))==nullptr) {
ViewGroup::onRestoreInstanceState(state);
return;
}
SavedState& ss = (SavedState&) state;
//ViewGroup::onRestoreInstanceState(ss.getSuperState());
mPendingCurrentItem = ss.mCurrentItem;
mPendingAdapterState = ss.mAdapterState;
}
#endif
void ViewPager2::restorePendingState() {
if (mPendingCurrentItem == RecyclerView::NO_POSITION) {
// No state to restore, or state is already restored
return;
}
RecyclerView::Adapter*adapter = getAdapter();
if (adapter == nullptr) {
return;
}
if (mPendingAdapterState != nullptr) {
/*if (adapter instanceof StatefulAdapter) {
((StatefulAdapter) adapter).restoreState(mPendingAdapterState);
}*/
mPendingAdapterState = nullptr;
}
// Now we have an adapter, we can clamp the pending current item and set it
mCurrentItem = std::max(0, std::min(mPendingCurrentItem, adapter->getItemCount() - 1));
mPendingCurrentItem = RecyclerView::NO_POSITION;
mRecyclerView->scrollToPosition(mCurrentItem);
//mAccessibilityProvider.onRestorePendingState();
}
#if 0
void ViewPager2::dispatchRestoreInstanceState(SparseArray<Parcelable*>& container) {
// RecyclerView changed an id, so we need to reflect that in the saved state
Parcelable* state = container.get(getId());
if (dynamic_cast<SavedState*>(state)) {
const int previousRvId = ((SavedState*) state)->mRecyclerViewId;
const int currentRvId = mRecyclerView->getId();
container.put(currentRvId, container.get(previousRvId));
container.remove(previousRvId);
}
ViewGroup::dispatchRestoreInstanceState(container);
// State of ViewPager2 and its child (RecyclerView) has been restored now
restorePendingState();
}
ViewPager2::SavedState::SavedState(Parcel& source) {
//super(source);
//readValues(source, nullptr);
}
ViewPager2::SavedState::SavedState(Parcelable superState) {
//super(superState);
}
/*void ViewPager2::SavedState::readValues(Parcel& source, ClassLoader loader) {
mRecyclerViewId = source.readInt();
mCurrentItem = source.readInt();
mAdapterState = source.readParcelable(loader);
}*/
void ViewPager2::SavedState::writeToParcel(Parcel& out, int flags) {
//ViewGroup::writeToParcel(out, flags);
out.writeInt(mRecyclerViewId);
out.writeInt(mCurrentItem);
out.writeParcelable(mAdapterState, flags);
}
#endif
void ViewPager2::setAdapter(RecyclerView::Adapter* adapter) {
RecyclerView::Adapter*currentAdapter = mRecyclerView->getAdapter();
//mAccessibilityProvider.onDetachAdapter(currentAdapter);
unregisterCurrentItemDataSetTracker(currentAdapter);
mRecyclerView->setAdapter(adapter);
mCurrentItem = 0;
restorePendingState();
//mAccessibilityProvider->onAttachAdapter(adapter);
registerCurrentItemDataSetTracker(adapter);
}
void ViewPager2::registerCurrentItemDataSetTracker(RecyclerView::Adapter*adapter) {
if (adapter != nullptr) {
adapter->registerAdapterDataObserver(mCurrentItemDataSetChangeObserver);
}
}
void ViewPager2::unregisterCurrentItemDataSetTracker(RecyclerView::Adapter*adapter) {
if (adapter != nullptr) {
adapter->unregisterAdapterDataObserver(mCurrentItemDataSetChangeObserver);
}
}
RecyclerView::Adapter* ViewPager2::getAdapter() {
return mRecyclerView->getAdapter();
}
void ViewPager2::onViewAdded(View* child) {
// TODO(b/70666620): consider adding a support for Decor views
FATAL(" does not support direct child views");
}
void ViewPager2::onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChild(mRecyclerView, widthMeasureSpec, heightMeasureSpec);
int width = mRecyclerView->getMeasuredWidth();
int height = mRecyclerView->getMeasuredHeight();
int childState = mRecyclerView->getMeasuredState();
width += getPaddingLeft() + getPaddingRight();
height += getPaddingTop() + getPaddingBottom();
width = std::max(width, getSuggestedMinimumWidth());
height = std::max(height, getSuggestedMinimumHeight());
setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
resolveSizeAndState(height, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
void ViewPager2::onLayout(bool changed, int l, int t, int w, int h) {
int width = mRecyclerView->getMeasuredWidth();
int height = mRecyclerView->getMeasuredHeight();
// TODO(b/70666626): consider delegating padding handling to the RecyclerView to avoid
// an unnatural page transition effect: http://shortn/_Vnug3yZpQT
mTmpContainerRect.left = getPaddingLeft();
mTmpContainerRect.width =w - getPaddingLeft() - getPaddingRight();
mTmpContainerRect.top = getPaddingTop();
mTmpContainerRect.height = h - getPaddingTop() - getPaddingBottom();
Gravity::apply(Gravity::TOP | Gravity::START, width, height, mTmpContainerRect, mTmpChildRect);
mRecyclerView->layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.width,
mTmpChildRect.height);
if (mCurrentItemDirty) {
updateCurrentItem();
}
}
void ViewPager2::updateCurrentItem() {
if (mPagerSnapHelper == nullptr) {
FATAL("Design assumption violated.");
}
View* snapView = mPagerSnapHelper->findSnapView(*mLayoutManager);
if (snapView == nullptr) {
return; // nothing we can do
}
const int snapPosition = mLayoutManager->getPosition(snapView);
if (snapPosition != mCurrentItem && getScrollState() == SCROLL_STATE_IDLE) {
/** TODO: revisit if push to {@link ScrollEventAdapter} / separate component */
mPageChangeEventDispatcher->onPageSelected(snapPosition);
}
mCurrentItemDirty = false;
}
int ViewPager2::getPageSize() {
RecyclerView* rv = mRecyclerView;
return getOrientation() == ORIENTATION_HORIZONTAL
? rv->getWidth() - rv->getPaddingLeft() - rv->getPaddingRight()
: rv->getHeight() - rv->getPaddingTop() - rv->getPaddingBottom();
}
void ViewPager2::setOrientation(int orientation) {
mLayoutManager->setOrientation(orientation);
//mAccessibilityProvider.onSetOrientation();
}
int ViewPager2::getOrientation() {
return mLayoutManager->getOrientation();
}
bool ViewPager2::isRtl() {
return mLayoutManager->getLayoutDirection() == View::LAYOUT_DIRECTION_RTL;
}
void ViewPager2::setCurrentItem(int item) {
setCurrentItem(item, true);
}
void ViewPager2::setCurrentItem(int item, bool smoothScroll) {
if (isFakeDragging()) {
FATAL("Cannot change current item when ViewPager2 is fake dragging");
}
setCurrentItemInternal(item, smoothScroll);
}
void ViewPager2::setCurrentItemInternal(int item, bool smoothScroll) {
// 1. Preprocessing (check state, validate item, decide if update is necessary, etc)
RecyclerView::Adapter* adapter = getAdapter();
if (adapter == nullptr) {
// Update the pending current item if we're still waiting for the adapter
if (mPendingCurrentItem != RecyclerView::NO_POSITION) {
mPendingCurrentItem = std::max(item, 0);
}
return;
}
if (adapter->getItemCount() <= 0) {
// Adapter is empty
return;
}
item = std::max(item, 0);
item = std::min(item, adapter->getItemCount() - 1);
if (item == mCurrentItem && mScrollEventAdapter->isIdle()) {
// Already at the correct page
return;
}
if (item == mCurrentItem && smoothScroll) {
// Already scrolling to the correct page, but not yet there. Only handle instant scrolls
// because then we need to interrupt the current smooth scroll.
return;
}
// 2. Update the item internally
double previousItem = mCurrentItem;
mCurrentItem = item;
//mAccessibilityProvider->onSetNewCurrentItem();
if (!mScrollEventAdapter->isIdle()) {
// Scroll in progress, overwrite previousItem with actual current position
previousItem = mScrollEventAdapter->getRelativeScrollPosition();
}
// 3. Perform the necessary scroll actions on RecyclerView
mScrollEventAdapter->notifyProgrammaticScroll(item, smoothScroll);
if (!smoothScroll) {
mRecyclerView->scrollToPosition(item);
return;
}
// For smooth scroll, pre-jump to nearby item for long jumps.
if (std::abs(item - previousItem) > 3) {
mRecyclerView->scrollToPosition(item > previousItem ? item - 3 : item + 3);
// TODO(b/114361680): call smoothScrollToPosition synchronously (blocked by b/114019007)
Runnable run([this,item](){
mRecyclerView->smoothScrollToPosition(item);
});
mRecyclerView->post(run);//new SmoothScrollToPosition(item, mRecyclerView));
} else {
mRecyclerView->smoothScrollToPosition(item);
}
}
int ViewPager2::getCurrentItem() const{
return mCurrentItem;
}
int ViewPager2::getScrollState() {
return mScrollEventAdapter->getScrollState();
}
bool ViewPager2::beginFakeDrag() {
return mFakeDragger->beginFakeDrag();
}
bool ViewPager2::fakeDragBy(float offsetPxFloat) {
return mFakeDragger->fakeDragBy(offsetPxFloat);
}
bool ViewPager2::endFakeDrag() {
return mFakeDragger->endFakeDrag();
}
bool ViewPager2::isFakeDragging() {
return mFakeDragger->isFakeDragging();
}
void ViewPager2::snapToPage() {
// Method copied from PagerSnapHelper#snapToTargetExistingView
// When fixing something here, make sure to update that method as well
View* view = mPagerSnapHelper->findSnapView(*mLayoutManager);
if (view == nullptr) {
return;
}
int snapDistance[2];
mPagerSnapHelper->calculateDistanceToFinalSnap(*mLayoutManager,*view,snapDistance);
//noinspection ConstantConditions
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
mRecyclerView->smoothScrollBy(snapDistance[0], snapDistance[1]);
}
}
void ViewPager2::setUserInputEnabled(bool enabled) {
mUserInputEnabled = enabled;
//mAccessibilityProvider.onSetUserInputEnabled();
}
bool ViewPager2::isUserInputEnabled() {
return mUserInputEnabled;
}
void ViewPager2::setOffscreenPageLimit(int limit) {
if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
FATAL("Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
}
mOffscreenPageLimit = limit;
// Trigger layout so prefetch happens through getExtraLayoutSize()
mRecyclerView->requestLayout();
}
int ViewPager2::getOffscreenPageLimit()const{
return mOffscreenPageLimit;
}
bool ViewPager2::canScrollHorizontally(int direction)const{
return mRecyclerView->canScrollHorizontally(direction);
}
bool ViewPager2::canScrollVertically(int direction)const{
return mRecyclerView->canScrollVertically(direction);
}
void ViewPager2::registerOnPageChangeCallback(OnPageChangeCallback callback) {
mExternalPageChangeCallbacks->addOnPageChangeCallback(callback);
}
void ViewPager2::unregisterOnPageChangeCallback(OnPageChangeCallback callback) {
mExternalPageChangeCallbacks->removeOnPageChangeCallback(callback);
}
void ViewPager2::setPageTransformer(PageTransformer* transformer) {
if (transformer != nullptr) {
if (!mSavedItemAnimatorPresent) {
mSavedItemAnimator = mRecyclerView->getItemAnimator();
mSavedItemAnimatorPresent = true;
}
mRecyclerView->setItemAnimator(nullptr);
} else {
if (mSavedItemAnimatorPresent) {
mRecyclerView->setItemAnimator(mSavedItemAnimator);
mSavedItemAnimator = nullptr;
mSavedItemAnimatorPresent = false;
}
}
// TODO: add support for reverseDrawingOrder: b/112892792
// TODO: add support for pageLayerType: b/112893074
if (transformer == mPageTransformerAdapter->getPageTransformer()) {
return;
}
mPageTransformerAdapter->setPageTransformer(transformer);
requestTransform();
}
void ViewPager2::requestTransform() {
if (mPageTransformerAdapter->getPageTransformer() == nullptr) {
return;
}
double relativePosition = mScrollEventAdapter->getRelativeScrollPosition();
int position = (int) relativePosition;
float positionOffset = (float) (relativePosition - position);
int positionOffsetPx = std::round(getPageSize() * positionOffset);
mPageTransformerAdapter->onPageScrolled(position, positionOffset, positionOffsetPx);
}
View& ViewPager2::setLayoutDirection(int layoutDirection) {
ViewGroup::setLayoutDirection(layoutDirection);
//mAccessibilityProvider.onSetLayoutDirection();
return *this;
}
#if 0
void ViewPager2::onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
mAccessibilityProvider.onInitializeAccessibilityNodeInfo(info);
}
bool ViewPager2::performAccessibilityAction(int action, Bundle arguments) {
if (mAccessibilityProvider.handlesPerformAccessibilityAction(action, arguments)) {
return mAccessibilityProvider.onPerformAccessibilityAction(action, arguments);
}
return super.performAccessibilityAction(action, arguments);
}
#endif
ViewPager2::RecyclerViewImpl::RecyclerViewImpl(Context* context,const AttributeSet&attr)
:RecyclerView(context,attr){
}
#if 0
CharSequence ViewPager2::RecyclerViewImpl::getAccessibilityClassName() {
if (mAccessibilityProvider.handlesRvGetAccessibilityClassName()) {
return mAccessibilityProvider.onRvGetAccessibilityClassName();
}
return super.getAccessibilityClassName();
}
void ViewPager2::RecyclerViewImpl::onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setFromIndex(mCurrentItem);
event.setToIndex(mCurrentItem);
mAccessibilityProvider.onRvInitializeAccessibilityEvent(event);
}
#endif
bool ViewPager2::RecyclerViewImpl::onTouchEvent(MotionEvent& event) {
return mVP->isUserInputEnabled() && RecyclerView::onTouchEvent(event);
}
bool ViewPager2::RecyclerViewImpl::onInterceptTouchEvent(MotionEvent& ev) {
return mVP->isUserInputEnabled() && RecyclerView::onInterceptTouchEvent(ev);
}
ViewPager2::LinearLayoutManagerImpl::LinearLayoutManagerImpl(Context* context,ViewPager2*vp)
:LinearLayoutManager(context){
mVP = vp;
}
#if 0
bool ViewPager2::LinearLayoutManagerImpl::performAccessibilityAction(@NonNull RecyclerView::Recycler recycler,
@NonNull RecyclerView::State state, int action, @Nullable Bundle args) {
if (mAccessibilityProvider.handlesLmPerformAccessibilityAction(action)) {
return mAccessibilityProvider.onLmPerformAccessibilityAction(action);
}
return super.performAccessibilityAction(recycler, state, action, args);
}
void ViewPager2::LinearLayoutManagerImpl::onInitializeAccessibilityNodeInfo(@NonNull RecyclerView::Recycler recycler,
@NonNull RecyclerView::State state, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(recycler, state, info);
mAccessibilityProvider.onLmInitializeAccessibilityNodeInfo(info);
}
#endif
void ViewPager2::LinearLayoutManagerImpl::calculateExtraLayoutSpace(RecyclerView::State& state,
int extraLayoutSpace[2]) {
const int pageLimit = mVP->getOffscreenPageLimit();
if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
// Only do custom prefetching of offscreen pages if requested
LinearLayoutManager::calculateExtraLayoutSpace(state, extraLayoutSpace);
return;
}
const int offscreenSpace = mVP->getPageSize() * pageLimit;
extraLayoutSpace[0] = offscreenSpace;
extraLayoutSpace[1] = offscreenSpace;
}
bool ViewPager2::LinearLayoutManagerImpl::requestChildRectangleOnScreen(RecyclerView& parent,
View& child,const Rect& rect, bool immediate, bool focusedChildVisible) {
return false; // users should use setCurrentItem instead
}
ViewPager2::PagerSnapHelperImpl::PagerSnapHelperImpl():PagerSnapHelper(){
}
View* ViewPager2::PagerSnapHelperImpl::findSnapView(RecyclerView::LayoutManager& layoutManager) {
// When interrupting a smooth scroll with a fake drag, we stop RecyclerView's scroll
// animation, which fires a scroll state change to IDLE. PagerSnapHelper then kicks in
// to snap to a page, which we need to prevent here.
// Simplifying that case: during a fake drag, no snapping should occur.
ViewPager2*vp = dynamic_cast<ViewPager2*>(mRecyclerView->getParent());
return vp->isFakeDragging() ? nullptr : PagerSnapHelper::findSnapView(layoutManager);
}
void ViewPager2::addItemDecoration(RecyclerView::ItemDecoration* decor) {
mRecyclerView->addItemDecoration(decor);
}
void ViewPager2::addItemDecoration(RecyclerView::ItemDecoration* decor, int index) {
mRecyclerView->addItemDecoration(decor, index);
}
RecyclerView::ItemDecoration* ViewPager2::getItemDecorationAt(int index) {
return mRecyclerView->getItemDecorationAt(index);
}
int ViewPager2::getItemDecorationCount() {
return mRecyclerView->getItemDecorationCount();
}
void ViewPager2::invalidateItemDecorations() {
mRecyclerView->invalidateItemDecorations();
}
void ViewPager2::removeItemDecorationAt(int index) {
mRecyclerView->removeItemDecorationAt(index);
}
void ViewPager2::removeItemDecoration(RecyclerView::ItemDecoration* decor) {
mRecyclerView->removeItemDecoration(decor);
}
#if 0
// TODO(b/141956012): Suppressed during upgrade to AGP 3.6.
class BasicAccessibilityProvider extends AccessibilityProvider {
@Override
public bool handlesLmPerformAccessibilityAction(int action) {
return (action == AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD
|| action == AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD)
&& !isUserInputEnabled();
}
@Override
public bool onLmPerformAccessibilityAction(int action) {
if (!handlesLmPerformAccessibilityAction(action)) {
throw new IllegalStateException();
}
return false;
}
@Override
public void onLmInitializeAccessibilityNodeInfo(
@NonNull AccessibilityNodeInfoCompat info) {
if (!isUserInputEnabled()) {
info.removeAction(AccessibilityActionCompat.ACTION_SCROLL_BACKWARD);
info.removeAction(AccessibilityActionCompat.ACTION_SCROLL_FORWARD);
info.setScrollable(false);
}
}
@Override
public bool handlesRvGetAccessibilityClassName() {
return true;
}
@Override
public CharSequence onRvGetAccessibilityClassName() {
if (!handlesRvGetAccessibilityClassName()) {
throw new IllegalStateException();
}
return "androidx.viewpager.widget.ViewPager";
}
}
class PageAwareAccessibilityProvider extends AccessibilityProvider {
private final AccessibilityViewCommand mActionPageForward =
new AccessibilityViewCommand() {
@Override
public bool perform(@NonNull View view,
@Nullable CommandArguments arguments) {
ViewPager2 viewPager = (ViewPager2) view;
setCurrentItemFromAccessibilityCommand(viewPager.getCurrentItem() + 1);
return true;
}
};
private final AccessibilityViewCommand mActionPageBackward =
new AccessibilityViewCommand() {
@Override
public bool perform(@NonNull View view,
@Nullable CommandArguments arguments) {
ViewPager2 viewPager = (ViewPager2) view;
setCurrentItemFromAccessibilityCommand(viewPager.getCurrentItem() - 1);
return true;
}
};
private RecyclerView::AdapterDataObserver mAdapterDataObserver;
@Override
public void onInitialize(@NonNull CompositeOnPageChangeCallback pageChangeEventDispatcher,
@NonNull RecyclerView recyclerView) {
ViewCompat.setImportantForAccessibility(recyclerView,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
mAdapterDataObserver = new DataSetChangeObserver() {
@Override
public void onChanged() {
updatePageAccessibilityActions();
}
};
if (ViewCompat.getImportantForAccessibility(ViewPager2.this)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
ViewCompat.setImportantForAccessibility(ViewPager2.this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
}
@Override
public bool handlesGetAccessibilityClassName() {
return true;
}
@Override
public String onGetAccessibilityClassName() {
if (!handlesGetAccessibilityClassName()) {
throw new IllegalStateException();
}
return "androidx.viewpager.widget.ViewPager";
}
@Override
public void onRestorePendingState() {
updatePageAccessibilityActions();
}
@Override
public void onAttachAdapter(@Nullable Adapter<?> newAdapter) {
updatePageAccessibilityActions();
if (newAdapter != null) {
newAdapter.registerAdapterDataObserver(mAdapterDataObserver);
}
}
@Override
public void onDetachAdapter(@Nullable Adapter<?> oldAdapter) {
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
}
}
@Override
public void onSetOrientation() {
updatePageAccessibilityActions();
}
@Override
public void onSetNewCurrentItem() {
updatePageAccessibilityActions();
}
@Override
public void onSetUserInputEnabled() {
updatePageAccessibilityActions();
if (Build.VERSION.SDK_INT < 21) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
}
@Override
public void onSetLayoutDirection() {
updatePageAccessibilityActions();
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
addCollectionInfo(info);
if (Build.VERSION.SDK_INT >= 16) {
addScrollActions(info);
}
}
@Override
public bool handlesPerformAccessibilityAction(int action, Bundle arguments) {
return action == AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD
|| action == AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
}
@Override
public bool onPerformAccessibilityAction(int action, Bundle arguments) {
if (!handlesPerformAccessibilityAction(action, arguments)) {
throw new IllegalStateException();
}
int nextItem = (action == AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD)
? getCurrentItem() - 1
: getCurrentItem() + 1;
setCurrentItemFromAccessibilityCommand(nextItem);
return true;
}
@Override
public void onRvInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
event.setSource(ViewPager2.this);
event.setClassName(onGetAccessibilityClassName());
}
void setCurrentItemFromAccessibilityCommand(int item) {
if (isUserInputEnabled()) {
setCurrentItemInternal(item, true);
}
}
void updatePageAccessibilityActions() {
ViewPager2 viewPager = ViewPager2.this;
@SuppressLint("InlinedApi")
final int actionIdPageLeft = android.R.id.accessibilityActionPageLeft;
@SuppressLint("InlinedApi")
final int actionIdPageRight = android.R.id.accessibilityActionPageRight;
@SuppressLint("InlinedApi")
final int actionIdPageUp = android.R.id.accessibilityActionPageUp;
@SuppressLint("InlinedApi")
final int actionIdPageDown = android.R.id.accessibilityActionPageDown;
ViewCompat.removeAccessibilityAction(viewPager, actionIdPageLeft);
ViewCompat.removeAccessibilityAction(viewPager, actionIdPageRight);
ViewCompat.removeAccessibilityAction(viewPager, actionIdPageUp);
ViewCompat.removeAccessibilityAction(viewPager, actionIdPageDown);
if (getAdapter() == null) {
return;
}
int itemCount = getAdapter().getItemCount();
if (itemCount == 0) {
return;
}
if (!isUserInputEnabled()) {
return;
}
if (getOrientation() == ORIENTATION_HORIZONTAL) {
bool isLayoutRtl = isRtl();
int actionIdPageForward = isLayoutRtl ? actionIdPageLeft : actionIdPageRight;
int actionIdPageBackward = isLayoutRtl ? actionIdPageRight : actionIdPageLeft;
if (mCurrentItem < itemCount - 1) {
ViewCompat.replaceAccessibilityAction(viewPager,
new AccessibilityActionCompat(actionIdPageForward, null), null,
mActionPageForward);
}
if (mCurrentItem > 0) {
ViewCompat.replaceAccessibilityAction(viewPager,
new AccessibilityActionCompat(actionIdPageBackward, null), null,
mActionPageBackward);
}
} else {
if (mCurrentItem < itemCount - 1) {
ViewCompat.replaceAccessibilityAction(viewPager,
new AccessibilityActionCompat(actionIdPageDown, null), null,
mActionPageForward);
}
if (mCurrentItem > 0) {
ViewCompat.replaceAccessibilityAction(viewPager,
new AccessibilityActionCompat(actionIdPageUp, null), null,
mActionPageBackward);
}
}
}
private void addCollectionInfo(AccessibilityNodeInfo info) {
int rowCount = 0;
int colCount = 0;
if (getAdapter() != null) {
if (getOrientation() == ORIENTATION_VERTICAL) {
rowCount = getAdapter().getItemCount();
} else {
colCount = getAdapter().getItemCount();
}
}
AccessibilityNodeInfoCompat nodeInfoCompat = AccessibilityNodeInfoCompat.wrap(info);
AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo =
AccessibilityNodeInfoCompat.CollectionInfoCompat.obtain(rowCount, colCount,
/* hierarchical= */false,
AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE);
nodeInfoCompat.setCollectionInfo(collectionInfo);
}
private void addScrollActions(AccessibilityNodeInfo info) {
final Adapter<?> adapter = getAdapter();
if (adapter == null) {
return;
}
int itemCount = adapter.getItemCount();
if (itemCount == 0 || !isUserInputEnabled()) {
return;
}
if (mCurrentItem > 0) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
}
if (mCurrentItem < itemCount - 1) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
}
info.setScrollable(true);
}
}
#endif
}/*endof namespace*/

279
src/gui/widgetEx/viewpager2.h Executable file
View File

@ -0,0 +1,279 @@
#ifndef __VIEWPAGER2_H__
#define __VIEWPAGER2_H__
#include <widgetEx/recyclerview/recyclerview.h>
#include <widgetEx/recyclerview/linearlayoutmanager.h>
#include <widgetEx/recyclerview/pagersnaphelper.h>
namespace cdroid{
class PagerSnapHelper;
class FakeDrag;
class ScrollEventAdapter;
class CompositeOnPageChangeCallback;
class PageTransformerAdapter;
class ViewPager2:public ViewGroup {
public:
class PageTransformer {
public:
virtual void transformPage(View& var1, float var2)=0;
};
class OnPageChangeCallback:public EventSet{
public:
CallbackBase<void,int,float,int> onPageScrolled;//(int position, float positionOffset, int positionOffsetPixels);
CallbackBase<void,int>onPageSelected;//(int position);
CallbackBase<void,int>onPageScrollStateChanged;//(int state);
};
protected:
/** Feature flag while stabilizing enhanced a11y */
static constexpr bool sFeatureEnhancedA11yEnabled = true;
public:
static constexpr int ORIENTATION_HORIZONTAL = RecyclerView::HORIZONTAL;
static constexpr int ORIENTATION_VERTICAL = RecyclerView::VERTICAL;
static constexpr int SCROLL_STATE_IDLE = 0;
static constexpr int SCROLL_STATE_DRAGGING = 1;
static constexpr int SCROLL_STATE_SETTLING = 2;
static constexpr int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;
private:
class RecyclerViewImpl;
class LinearLayoutManagerImpl;
class RecyclerViewImpl;
class PagerSnapHelperImpl;
RecyclerView::AdapterDataObserver* mCurrentItemDataSetChangeObserver;
// reused in layout(...)
Rect mTmpContainerRect;
Rect mTmpChildRect;
Runnable mSmoothScrollToPositionRunnable;
CompositeOnPageChangeCallback* mExternalPageChangeCallbacks;
LinearLayoutManager* mLayoutManager;
int mPendingCurrentItem = RecyclerView::NO_POSITION;
Parcelable* mPendingAdapterState;
PagerSnapHelper* mPagerSnapHelper;
CompositeOnPageChangeCallback* mPageChangeEventDispatcher;
FakeDrag* mFakeDragger;
PageTransformerAdapter* mPageTransformerAdapter;
RecyclerView::ItemAnimator* mSavedItemAnimator = nullptr;
bool mSavedItemAnimatorPresent = false;
bool mUserInputEnabled = true;
int mOffscreenPageLimit = OFFSCREEN_PAGE_LIMIT_DEFAULT;
protected:
int mCurrentItem;
bool mCurrentItemDirty = false;
RecyclerViewImpl* mRecyclerView;
ScrollEventAdapter* mScrollEventAdapter;
//AccessibilityProvider* mAccessibilityProvider; // to avoid creation of a synthetic accessor
private:
class RecyclerViewImpl;
void initialize(Context* context, const AttributeSet& attrs);
//RecyclerView::OnChildAttachStateChangeListener enforceChildFillListener();
void setOrientation(Context* context,const AttributeSet& attrs);
void restorePendingState();
void unregisterCurrentItemDataSetTracker(RecyclerView::Adapter*adapter);
protected:
friend class FakeDrag;
friend class ScrollEventAdapter;
//Parcelable* onSaveInstanceState()override;
//void onRestoreInstanceState(Parcelable& state)override;
//void dispatchRestoreInstanceState(SparseArray<Parcelable*>& container)override;
void onMeasure(int widthMeasureSpec, int heightMeasureSpec)override;
void onLayout(bool changed, int l, int t, int w, int h)override;
void onViewAdded(View* child)override;
void updateCurrentItem();
int getPageSize();
void setCurrentItemInternal(int item, bool smoothScroll);
void snapToPage();
public:
ViewPager2(Context* context, const AttributeSet& attrs);
//CharSequence getAccessibilityClassName()override;
void setAdapter(RecyclerView::Adapter* adapter);
void registerCurrentItemDataSetTracker(RecyclerView::Adapter* adapter);
RecyclerView::Adapter* getAdapter();
void setOrientation(int orientation);
int getOrientation();
bool isRtl();
void setCurrentItem(int item);
void setCurrentItem(int item, bool smoothScroll);
int getCurrentItem()const;
int getScrollState();
bool beginFakeDrag();
bool fakeDragBy(float offsetPxFloat);
bool endFakeDrag();
bool isFakeDragging();
void setUserInputEnabled(bool enabled);
bool isUserInputEnabled();
void setOffscreenPageLimit(int limit);
int getOffscreenPageLimit()const;
virtual bool canScrollHorizontally(int direction)const;
virtual bool canScrollVertically(int direction)const;
void registerOnPageChangeCallback(OnPageChangeCallback callback);
void unregisterOnPageChangeCallback(OnPageChangeCallback callback);
void setPageTransformer(PageTransformer* transformer);
void requestTransform();
View&setLayoutDirection(int layoutDirection)override;
//void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)override;
bool performAccessibilityAction(int action, Bundle arguments);
#if 0
#endif
void addItemDecoration(RecyclerView::ItemDecoration* decor);
void addItemDecoration(RecyclerView::ItemDecoration* decor, int index);
RecyclerView::ItemDecoration* getItemDecorationAt(int index);
int getItemDecorationCount();
void invalidateItemDecorations();
void removeItemDecorationAt(int index);
void removeItemDecoration(RecyclerView::ItemDecoration* decor);
#if 0
private class AccessibilityProvider {
void onInitialize(@NonNull CompositeOnPageChangeCallback pageChangeEventDispatcher,RecyclerView recyclerView);
virtual bool handlesGetAccessibilityClassName();
virtual String onGetAccessibilityClassName() {
virtual void onRestorePendingState(){}
virtual void onAttachAdapter(@Nullable Adapter<?> newAdapter){}
virtual void onDetachAdapter(@Nullable Adapter<?> oldAdapter){}
virtual void onSetOrientation(){}
virtual void onSetNewCurrentItem(){}
virtual void onSetUserInputEnabled(){}
virtual void onSetLayoutDirection(){}
virtual void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { }
virtual bool handlesPerformAccessibilityAction(int action, Bundle arguments) {
return false;
}
virtual bool onPerformAccessibilityAction(int action, Bundle arguments) {
throw new IllegalStateException("Not implemented.");
}
virtual void onRvInitializeAccessibilityEvent(@NonNull AccessibilityEvent event){}
virtual bool handlesLmPerformAccessibilityAction(int action) {
return false;
}
virtual bool onLmPerformAccessibilityAction(int action) {
throw new IllegalStateException("Not implemented.");
}
virtual void onLmInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfoCompat info) {
}
virtual bool handlesRvGetAccessibilityClassName() {
return false;
}
virtual CharSequence onRvGetAccessibilityClassName() {
throw new IllegalStateException("Not implemented.");
}
};
class BasicAccessibilityProvider:public AccessibilityProvider {
public:
bool handlesLmPerformAccessibilityAction(int action)override;
bool onLmPerformAccessibilityAction(int action)override
void onLmInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info)override
bool handlesRvGetAccessibilityClassName()override
CharSequence onRvGetAccessibilityClassName()override;
};
class PageAwareAccessibilityProvider:public AccessibilityProvider {
private:
AccessibilityViewCommand mActionPageForward;
AccessibilityViewCommand mActionPageBackward;
RecyclerView::AdapterDataObserver mAdapterDataObserver;
void addCollectionInfo(AccessibilityNodeInfo info);
void addScrollActions(AccessibilityNodeInfo info);
protected:
void setCurrentItemFromAccessibilityCommand(int item);
void updatePageAccessibilityActions();
public:
void onInitialize(@NonNull CompositeOnPageChangeCallback pageChangeEventDispatcher,
@NonNull RecyclerView recyclerView)override;
bool handlesGetAccessibilityClassName()override;
String onGetAccessibilityClassName()override;
void onRestorePendingState()override;
void onAttachAdapter(@Nullable Adapter<?> newAdapter)override;
void onDetachAdapter(@Nullable Adapter<?> oldAdapter)override;
void onSetOrientation()override;
void onSetNewCurrentItem()override;
void onSetUserInputEnabled()override;
void onSetLayoutDirection()override;
void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)override;
bool handlesPerformAccessibilityAction(int action, Bundle arguments)override;
bool onPerformAccessibilityAction(int action, Bundle arguments)override;
void onRvInitializeAccessibilityEvent(@NonNull AccessibilityEvent event)override;
};
#endif
};/*endof ViewPager2*/
#if 0
class ViewPager2::SavedState:public AbsSavedState{//BaseSavedState {
protected:
int mRecyclerViewId;
int mCurrentItem;
Parcelable* mAdapterState;
friend ViewPager2;
public:
SavedState(Parcel& source);
SavedState(Parcelable& superState);
//void readValues(Parcel source, ClassLoader loader);//private
void writeToParcel(Parcel& out, int flags);
};
#endif
class ViewPager2::RecyclerViewImpl:public RecyclerView {
private:
friend ViewPager2;
ViewPager2*mVP;
public:
RecyclerViewImpl(Context* context,const AttributeSet&);
//CharSequence getAccessibilityClassName()override;
//void onInitializeAccessibilityEvent(AccessibilityEvent event)override;
bool onTouchEvent(MotionEvent& event)override;
bool onInterceptTouchEvent(MotionEvent& ev)override;
};
class ViewPager2::LinearLayoutManagerImpl:public LinearLayoutManager {
protected:
ViewPager2*mVP;
protected:
void calculateExtraLayoutSpace(RecyclerView::State& state,int extraLayoutSpace[2])override;
public:
LinearLayoutManagerImpl(Context* context,ViewPager2*vp);
/*bool performAccessibilityAction(RecyclerView::Recycler& recycler,
RecyclerView::State& state, int action, Bundle args)override;
void onInitializeAccessibilityNodeInfo(RecyclerView::Recycler& recycler,
RecyclerView::State& state, AccessibilityNodeInfo& info)override;*/
bool requestChildRectangleOnScreen(RecyclerView& parent,View& child,const Rect& rect, bool immediate,bool focusedChildVisible)override;
};
class ViewPager2::PagerSnapHelperImpl:public PagerSnapHelper {
public:
PagerSnapHelperImpl();
View* findSnapView(RecyclerView::LayoutManager& layoutManager);
};
class ViewPager2DataSetChangeObserver:public RecyclerView::AdapterDataObserver {
public:
virtual void onChanged()=0;
virtual void onItemRangeChanged(int positionStart, int itemCount)final{
onChanged();
}
virtual void onItemRangeChanged(int positionStart, int itemCount,
Object* payload)final{
onChanged();
}
void onItemRangeInserted(int positionStart, int itemCount) {
onChanged();
}
void onItemRangeRemoved(int positionStart, int itemCount)final{
onChanged();
}
void onItemRangeMoved(int fromPosition, int toPosition, int itemCount)final{
onChanged();
}
};
}/*endof namespace*/
#endif/*__VIEWPAGER2_H__*/