上来先谈需求,好像整的我跟产品似的。我可不是产品**,嘿嘿!但是没有办法,有需要才有实现,有实现才有问题,有问题才有我的总结。这文采牛逼不牛逼。
问题1:
需求:点击图片实现查看大图,可以左右切换
顶部是标题栏(左中右的顺序):返回键,标题(当前图片位置/图片总数),更多按钮
中间是图片
底部是底部导航栏:其实只有一个下载按钮
这个只是我需求的大致样貌,我真正的需求是:点击更多按钮弹出dialog(保存,删除),就是删除功能。
如果只有一张图,删除之后推出当前页面返回上一页
如果是多张图,删除最后一张图,选择前一张;删除中间页图片,选择下一张。
好的,就是这么个需求。又很简单是不是?
实现:删除的时候,将图片集合移除该图片url,传回到adapter,调用notifyDataSetChanged();
是不是?是不是?你么就说是不是?
不是!!!!!!!没有任何作用。为啥子呢?没办法,看源代码!
/**
PagerAdapter
* This method should be called by the application if the data backing this adapter has changed
* and associated views should update.
*/
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
从上述方法可以看出:底层采用的是观察者模式,但是mViewPagerObserver是什么呢?
PagerAdpapter的方法
void setViewPagerObserver(DataSetObserver observer) {
synchronized (this) {
mViewPagerObserver = observer;
}
}
从上述方法可以看出,mViewPagerObserver是在这个方法赋值。那这个方法又是在哪里调用的呢?
ViewPager的方法
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public void setAdapter(@Nullable PagerAdapter adapter) {
if (mAdapter != null) {
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
populate();
} else {
requestLayout();
}
}
// Dispatch the change to any listeners
if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
}
}
}
这个代码稍微有点长啊,咱们一点点分析。但是不管怎么说,你看见它是不是很眼熟?setAdapter(),不就是RecyclerView/ListView/GridView
中的setAdapter吗?好了,不说废话了。开始分析
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
在这里,创建mObserver也就是PagerObserver,那PagerObserver又是什么呢?
ViewPager的代码
private class PagerObserver extends DataSetObserver {
PagerObserver() {
}
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
可以看出,这是继承DataSetObserverde 。而且里边包含两个方法:onChanged和onInvalidated(),onChanged()是不是很眼熟?
是的,就是在PagerAdapter的notifyDataSetChanged中调用的
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
而且在onChanged()和onInvalidated()方法中都调用了dataSetChanged(),我们来看一下代码实现啊
ViewPager代码,又是稍微有点长哈,没事。我们一点点来看。
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter is non-null.
//获取item数量
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
//一个逻辑判断,判断是否需要计算item数量
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
&& mItems.size() < adapterCount;
int newCurrItem = mCurItem;
//是否正在更新
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
可以看到上边有两个常量:PagerAdpater.POSITION_UNCHANGED 和 PagerAdapter.POSITOIN_NONE;好好理解,
第一个参数是:位置没有发生改变,
第二个参数是:发生改变,需要移除该item
由上可以看出,我们需要动态改变item的位置,如下就是解决办法(在我们自定义的adapter中)
@Override
public int getItemPosition(@NonNull Object object) {
if (!isNotify) {
return super.getItemPosition(object);
}else
{
return POSITION_NONE;
}
}
public void setDataAndNotify(List<String> urls,boolean isNotify)
{
this.urls = new ArrayList<>();
this.isNotify = isNotify;
if (urls != null)
{
this.urls.addAll(urls);
super.notifyDataSetChanged();
isNotify = false;
}
}
问题2:
setCurrentItem()会不会触发viewPager.addOnPagerChangedListener()
官方说明啊,setCurrentItem() 和 滑动item会触发
然后我就遇到了,setCurrentItem没有触发。还是老样子,看源码吧
ViewPager代码
/**
* Set the currently selected page. If the ViewPager has already been through its first
* layout with its current adapter there will be a smooth animated transition between
* the current item and the specified item.
*
* @param item Item index to select
*/
public void setCurrentItem(int item) {
mPopulatePending = false;
setCurrentItemInternal(item, !mFirstLayout, false);
}
以上方法,只有两句话:
mPopulatePending = false;
setCurrentItemInternal(item,!mFirstLayout,false);这个是我们看的重点
ViewPager代码
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
setCurrentItemInternal(item, smoothScroll, always, 0);
}
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
if (mAdapter == null || mAdapter.getCount() <= 0) {
setScrollingCacheEnabled(false);
return;
}
if (!always && mCurItem == item && mItems.size() != 0) {
setScrollingCacheEnabled(false);
return;
}
if (item < 0) {
item = 0;
} else if (item >= mAdapter.getCount()) {
item = mAdapter.getCount() - 1;
}
final int pageLimit = mOffscreenPageLimit;
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
for (int i = 0; i < mItems.size(); i++) {
mItems.get(i).scrolling = true;
}
}
//哎呀,这句话很重要啊,看当前的item和需要设置的item是不是同一个
final boolean dispatchSelected = mCurItem != item;
if (mFirstLayout) {
// We don't have any idea how big we are yet and shouldn't have any pages either.
// Just set things up and let the pending layout handle things.
mCurItem = item;
//如果为true,则调用类似事件分发的方法,我们去看一下方法的实现
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
requestLayout();
} else {
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
}
ViewPager代码
private void dispatchOnPageSelected(int position) {
if (mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(position);
}
if (mOnPageChangeListeners != null) {
for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
OnPageChangeListener listener = mOnPageChangeListeners.get(i);
if (listener != null) {
//哎呀,又看到熟悉的地方了,这不就是listener中的onPagerSelected吗?
listener.onPageSelected(position);
}
}
}
if (mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(position);
}
}
综上所述,我之所会出现上述问题。是因为我调用setCurrentItem(index)和当前的position是一致的。所以才会出现如下情况。
好了,又一个问题解决了,给自己点个赞吧。如果有什么不对的地方,希望大家踊跃指出。
感谢,在学习的道路上进步。