首页 文章 精选 留言 我的

精选列表

搜索[内容治理],共10000篇文章
优秀的个人博客,低调大师

FragmentPagerAdapter 页面类型、数量、内容更新问题

场景 存在一种需求,当用户系统中,属于某一组织的用户登录之后(或者账户切换),要求主页面显示不同的ViewPager + Fragment组合,并且要求app无需退出就能刷新组合以及组合中的页面。 此外,为了保证Fragment和Fragment中View不必要的inflate和渲染,要求尽可能重用已存在的Fragment和View。显然FragmentPagerAdapter是首选。但是存在三个问题: 1、FragmentPagerAdapter默认无法更新,需要重写getItemPosition,返回值为PagerAdapter.POSITION_NONE才可以更新 2、重用的Fragment设置参数无法重新初始化 3、重用的Fragment类型和新的Fragment类型存在不匹配问题,如旧的UserFragment页面,但是新的要求是ListFragment,所以类型存在问题。 解决方案 我们需要重写FragmentPagerAdapter,但问题是存在各种不方便的因素,因此,我们需要自定义FragmentPagerAdapter。 public abstract class CustomFragmentPagerAdapter extends PagerAdapter { private static final String TAG = "FragmentPagerAdapter"; private static final boolean DEBUG = false; private final FragmentManager mFragmentManager; private FragmentTransaction mCurTransaction = null; private Fragment mCurrentPrimaryItem = null; private final LongSparseArray<String> fragmentViewTypeManager = new LongSparseArray<String>(); public CustomFragmentPagerAdapter(FragmentManager fm) { mFragmentManager = fm; } @Override public void startUpdate(ViewGroup container) { if (container.getId() == View.NO_ID) { //viewPager必须赋值ID,否则无法添加fragment throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id"); } } @SuppressWarnings("ReferenceEquality") @Override public Object instantiateItem(ViewGroup container, int position) { mCurTransaction = beginTransaction(); final long itemId = getItemId(position); final int count = this.getFragmentTypeCount(); int fragmentType = 0; if(count>0){ fragmentType = getItemFragmentType(position); } if(fragmentType>0 && fragmentType>=count){ throw new IllegalArgumentException("{fragmentType's number >= fragmentTypeCount's number} is illegal"); } // 生成tag,用于保存和标记每个位置的fragment final String name = makeFragmentName(container.getId(), itemId); //生成tag final String oldFragmentName = fragmentViewTypeManager.get(fragmentType); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { final String fragmentClassName = fragment.getClass().getName(); if(!fragmentClassName.equals(oldFragmentName)) { //如果发现新旧类型不一致,移除旧类型 if (DEBUG) Log.v(TAG, "Removeing item #" + itemId + ": f=" + fragment); mCurTransaction.remove(fragment); //获取新类型 fragment = getItem(null,position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment,name); }else { Fragment newFragment = getItem(fragment,position); //获取newFragment ,如果2次fragment不一致,移除旧的fragment if(newFragment!=fragment){ if (DEBUG) Log.v(TAG, "Removeing item #" + itemId + ": f=" + fragment); mCurTransaction.remove(fragment); fragment = newFragment; if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment,name); }else { //如果获取到fragment与原来的是同一个,attach即可 if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } } } else { fragment = getItem(fragment,position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment,name); } if(fragment!=null){ //保存类型,用来校验缓存的正确性 fragmentViewTypeManager.put(fragmentType,fragment.getClass().getName()); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { mCurTransaction = beginTransaction(); if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment)object).getView()); mCurTransaction.detach((Fragment)object); //dattach fragment } @SuppressWarnings("ReferenceEquality") @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (fragment != mCurrentPrimaryItem) { if (mCurrentPrimaryItem != null) { mCurrentPrimaryItem.setMenuVisibility(false); mCurrentPrimaryItem.setUserVisibleHint(false); } if (fragment != null) { fragment.setMenuVisibility(true); fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; //设置当前的fragment } } @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitNowAllowingStateLoss(); //提交,注意该方法将任务加入到mainLooper中,可能产生延迟 mCurTransaction = null; } } @Override public boolean isViewFromObject(View view, Object object) { return ((Fragment)object).getView() == view; } @Override public Parcelable saveState() { return null; } @Override public void restoreState(Parcelable state, ClassLoader loader) { } /** * * 获取每个fragment的id,注意保证唯一性 */ public long getItemId(int position) { return position; } //生成tag public static String makeFragmentName(int viewId, long id) { return "android:switcher:" + viewId + ":" + id; } public FragmentTransaction beginTransaction(){ if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } return mCurTransaction; } public FragmentManager getFragmentManager(){ return mFragmentManager; } /** * 获取当前位置的fragment */ public abstract Fragment getItem(Fragment contentFragment,int position); /** * 获取当前位置的type FragmentType */ public abstract int getItemFragmentType(int position); /** * 获取当前类型的数量 FragmentCount */ public abstract int getFragmentTypeCount(); /** * 在ViewPager中调用,告诉ViewPager该位置的Fragment是可以被替换和更新的 * 这里人可以继续优化,由于ViewPager.LayoutParams中的position是非public的,因此要优化可以在该类的基类中完成 */ @Override public int getItemPosition(Object object) { if(!(object instanceOf Fragment)) { return PagerAdapter.POSITION_NONE; } return PagerAdapter.POSITION_UNCHANGED; } public boolean isEmpty(){ return getCount()==0; } } 到这里,我们便可以实现他的子类 static class MyPagerAdapter extends CustomFragmentPagerAdapter{ private ArrayList<FragmentTabEntity> dataEntities; private final String TAG_NAME = "MyPagerAdapter "; public MyPagerAdapter(FragmentManager fm,List<FragmentTabEntity> dataEntities) { super(fm); this.dataEntities = new ArrayList<>(); this.dataEntities.addAll(dataEntities); } @SuppressWarnings("unchecked") public void updateDataEntities(List<FragmentTabEntity> dataEntities) { this.dataEntities.clear(); if(dataEntities!=null && dataEntities.size()>0){ this.dataEntities.addAll(dataEntities); } this.notifyDataSetChanged(); } @Override public CharSequence getPageTitle(int position) { final FragmentTabEntity entity = dataEntities.get(position); return entity.getTitle(); } @Override public int getItemFragmentType(int position) { final FragmentTabEntity dataEntity = dataEntities.get(position); return dataEntity.getType(position); //获取类型,注意,最大值不能大于getFragmentTypeCount() } @Override public int getFragmentTypeCount() { return FragmentTabEntity.getTotalTypeCount(); //获取所有fragment的类型数量,一般是固定值,主要看程序实现方式了 } @Override public Fragment getItem(Fragment contentFragment,int position) { final int fragmentType = getItemFragmentType(position); final FragmentTabEntity dataEntity = dataEntities.get(position); BaseFragment fragment = null; if(contentFragment==null) { if (fragmentType == 0) { fragment = new IndexFragment(); } else if(fragmentType ==1){ fragment = new UserFragment(); } else if(fragmentType ==2){ fragment = new WebFragment(); }else if(fragmentType ==3){ fragment = new ListFragment(); } }else{ fragment = (BaseFragment) contentFragment; } fragment.setPosition(position); if(fragment!=null) { Bundle fb = new Bundle(); fb.putString(BaseFragment.KEY_TYPE, dataEntity.getType()); fb.putString(BaseFragment.KEY_TITLE, dataEntity.getTitle()); fragment.setNoneStateArguments(fb); //使用非状态参数传递方法 } return fragment; } @Override public int getCount() { return dataEntities.size(); } } 使用方法 private MyPagerAdapter mTabPagerAdapter; public void updatePager(List<FragmentTabEntity> data) { if((pager.getAdapter() instanceof MyPagerAdapter)){ mTabPagerAdapter = (MyPagerAdapter) pager.getAdapter(); } if(mTabPagerAdapter==null){ mTabPagerAdapter = new MyPagerAdapter(getChildFragmentManager(),data); pager.setAdapter(mTabPagerAdapter); }else{ mTabPagerAdapter.updateDataEntities(data); } } Fragment ViewCache问题 & 生命周期问题 到这一步事实上我们的自定义FragmentPagerAdapter已经完成了,但是这里还存在不完美的问题,那就是Fragment中添加了View Cache的情况,此外,对于生命周期的控制,可能或多或少出现旧页面向新页面过渡时闪烁问题。 1、View Cache 问题 先来看看这种Fragment的定义方式 public class BaseFragment extends Fragment{ private SoftReference<View> mRootViewCache = null; //实现viewcache private boolean isFinishedInflated = false; private Bundle mNoneStateArguments; private int position = -1; public void setPosition(int position){ this.position = position; } public int getPosition(){ return this.position; } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ View root = null; if(!cacheIsEmpty()){ root = mRootViewCache.get(); } if(root==null){ root = inflater.inflate(R.layout.base_view_layout, container, false); root.findViewById(R.id.toolbar).setVisibility(View.GONE); mRootViewCache = new SoftReference<View>(root); } return root; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); try { isFinishedInflated = true; renderFragmentView(view); } catch (Exception e) { e.printStackTrace(); } } private boolean cacheIsEmpty(){ return mRootViewCache==null || mRootViewCache.get()==null; } @Override public void onResume() { super.onResume(); if(getUserVisibleHint() ){ onFragmetShow(); } } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if(!isFinishedInflated) return; if( getUserVisibleHint()){ onFragmetShow(); }else if(isResumed()){ onFragmetHide(); } } @Override public void onStop() { super.onStop(); if(getUserVisibleHint()){ onFragmetHide(); } } public void onFragmetShow(){ if(getView()==null) return ; getView().post(new Runnable(){ public void run(){ //这里可以用来获取Fragment的参数,然后更新 } }); } public void onFragmetHide(){ } public void setNoneStateArguments(Bundle bundle){ //解决已初始化状态的参数刷新问题 this.mNoneStateArguments = bundle; } public void getNoneStateArguments(){ return this.mNoneStateArguments!=null? this.mNoneStateArguments:new Bundle(); } } 对于原生页面,新旧页面闪烁并不是很明显,但是对于Webview页面,这种闪烁很明显,导致该问题的原因是View Cache,因此,我们需要在Fragment中添加clearView方法来清空一下Cache public void clearView() { if(mRootViewCache!=null){ mRootViewCache.clear(); } } 在MyPagerAdapter的getItem方法中,我们有必要植入一个flag @Override public Fragment getItem(Fragment contentFragment,int position) { final int fragmentType = getItemFragmentType(position); final FragmentTabEntity dataEntity = dataEntities.get(position); BaseFragment fragment = null; if(contentFragment==null) { if (viewType == 0) { fragment = new IndexFragment(); } else if(fragmentType ==1){ fragment = new UserFragment(); } else if(fragmentType ==2){ fragment = new WebFragment(); }else if(fragmentType ==3){ fragment = new ListFragment(); } }else{ fragment = (BaseFragment) contentFragment; } final Bundle fa = fragment.getArguments(); if(fa!=null) { final String oldTag = fa.getString("md5", ""); if (!TextUtils.isEmpty(oldTag) && !oldUrl.oldTag(dataEntity.getMd5())) { fragment.clearView(); //如果tag不一致,清空一下view cache } } if(fragment!=null) { Bundle fb = new Bundle(); fb.putString("md5",dataEntity.getMd5()); //植入新的md5 tag fb.putString(BaseFragment.KEY_TYPE, dataEntity.getType()); fb.putString(BaseFragment.KEY_TITLE, dataEntity.getTitle()); fragment.setArguments(fb); } return fragment; } 2、生命周期问题 关于onFragmentShow与onFragmentHide的生命周期用法,请参考《Fragment页面切换》,这里我们主要说一下mainLooper问题 public void onFragmetShow(){ if(getView()==null) return ; getView().post(new Runnable(){ public void run(){ //这里可以用来获取Fragment的参数,然后更新 } }); } 如果要更新UI,我们建议这里使用post将消息发送到mainLooper,为什么要这样呢? 主要原因是FragmentPagerAdapter的finishUpdate中使用了commit方法,这个方法是将任务发送到mainLooper的队列中,而不是立即执行。 @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitNowAllowingStateLoss(); mCurTransaction = null; } } 基于队列的先进先出,FragmentTransaction将更新消息加入到Fragment add/attach消息之后,我们如果直接获取argument可能出现数据不一致的问题,因此我们需要将我们的方法作为任务同样放入到mainLooper中。如果不这么做,可能导致获取到的argument是旧的,导致我们更新时使用了旧的参数。当然,可以参考《Android Fragment重复添加问题解决方法》,原理基本相同。 以上是一般常见的问题,至于其他问题,可以留言。

优秀的个人博客,低调大师

Android--监测文件夹内容变化

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/77932391 一. 概述: android.os包下的FileObserver类是一个用于监听文件访问、创建、修改、删除、移动等操作的监听器,基于linux的INotify。FileObserver是个抽象 类,必须继承它才能使用。每个FileObserver对象监听一个单独的文件或者文件夹,如果监视的是一个文件夹,那么文件夹下所有的文件和级联子目录的改 变都会触发监听的事件。 二. 监听的事件类型: ACCESS,即文件被访问 MODIFY,文件被 修改 ATTRIB,文件属性被修改,如 chmod、chown、touch 等 CLOSE_WRITE,可写文件被 close CLOSE_NOWRITE,不可写文件被 close OPEN,文件被 open MOVED_FROM,文件被移走,如 mv MOVED_TO,文件被移来,如 mv、cp CREATE,创建新文件 DELETE,文件被删除,如 rm DELETE_SELF,自删除,即一个可执行文件在执行时删除自己 MOVE_SELF,自移动,即一个可执行文件在执行时移动自己 CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) ALL_EVENTS,包括上面的所有事件 三.实例 import com.example.androidemail.R; import com.example.androidemail.R.layout; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.FileObserver; public class AndroidFileListenerActivity extends Activity { private FileObserver mFileObserver; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(null == mFileObserver) { mFileObserver = new SDCardFileObserver(Environment.getExternalStorageDirectory().getPath()); mFileObserver.startWatching(); //开始监听 } } public void onDestory() { if(null != mFileObserver) mFileObserver.stopWatching(); //停止监听 } static class SDCardFileObserver extends FileObserver { //mask:指定要监听的事件类型,默认为FileObserver.ALL_EVENTS public SDCardFileObserver(String path, int mask) { super(path, mask); } public SDCardFileObserver(String path) { super(path); } @Override public void onEvent(int event, String path) { final int action = event & FileObserver.ALL_EVENTS; switch (action) { case FileObserver.ACCESS: System.out.println("event: 文件或目录被访问, path: " + path); break; case FileObserver.DELETE: System.out.println("event: 文件或目录被删除, path: " + path); break; case FileObserver.OPEN: System.out.println("event: 文件或目录被打开, path: " + path); break; case FileObserver.MODIFY: System.out.println("event: 文件或目录被修改, path: " + path); break; case FileObserver.CREATE: System.out.println("event: 文件或目录被创建, path: " + path); break; } } } } onEvent是回调,系统监听到事件后会触发此事件,参数event就是上面所说的事件类型,参数path就是触发事件的目录,鉴定只针对于该层目录,其他层次无效。 我们大多数需要监听path目录下的所有文件对象的相关操作,那该如何是好呢?解决问题方法之一就是重新实现FileObserver类, 下面是对FileObserver类的重写实现过程 import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Stack; import android.os.FileObserver; import android.util.Log; @SuppressWarnings(value = { "rawtypes", "unchecked" }) public class MultiFileObserver extends FileObserver { /** Only modification events */ public static int CHANGES_ONLY = CREATE | MODIFY |DELETE | CLOSE_WRITE | DELETE_SELF | MOVE_SELF | MOVED_FROM | MOVED_TO; private List<SingleFileObserver> mObservers; private String mPath; private int mMask; public MuityFileObserver(String path) { this(path, ALL_EVENTS); } public MuityFileObserver(String path, int mask) { super(path, mask); mPath = path; mMask = mask; } @Override public void startWatching() { if (mObservers != null) return; mObservers = new ArrayList<SingleFileObserver>(); Stack<String> stack = new Stack<String>(); stack.push(mPath); while (!stack.isEmpty()) { String parent = stack.pop(); mObservers.add(new SingleFileObserver(parent, mMask)); File path = new File(parent); File[] files = path.listFiles(); if (null == files) continue; for (File f : files) { if (f.isDirectory() && !f.getName().equals(".") && !f.getName().equals("..")) { stack.push(f.getPath()); } } } for (int i = 0; i < mObservers.size(); i++) { SingleFileObserver sfo = mObservers.get(i); sfo.startWatching(); } }; @Override public void stopWatching() { if (mObservers == null) return; for (int i = 0; i < mObservers.size(); i++) { SingleFileObserver sfo = mObservers.get(i); sfo.stopWatching(); } mObservers.clear(); mObservers = null; }; @Override public void onEvent(int event, String path) { switch (event) { case FileObserver.ACCESS: Log.i("RecursiveFileObserver", "ACCESS: " + path); break; case FileObserver.ATTRIB: Log.i("RecursiveFileObserver", "ATTRIB: " + path); break; case FileObserver.CLOSE_NOWRITE: Log.i("RecursiveFileObserver", "CLOSE_NOWRITE: " + path); break; case FileObserver.CLOSE_WRITE: Log.i("RecursiveFileObserver", "CLOSE_WRITE: " + path); break; case FileObserver.CREATE: Log.i("RecursiveFileObserver", "CREATE: " + path); break; case FileObserver.DELETE: Log.i("RecursiveFileObserver", "DELETE: " + path); break; case FileObserver.DELETE_SELF: Log.i("RecursiveFileObserver", "DELETE_SELF: " + path); break; case FileObserver.MODIFY: Log.i("RecursiveFileObserver", "MODIFY: " + path); break; case FileObserver.MOVE_SELF: Log.i("RecursiveFileObserver", "MOVE_SELF: " + path); break; case FileObserver.MOVED_FROM: Log.i("RecursiveFileObserver", "MOVED_FROM: " + path); break; case FileObserver.MOVED_TO: Log.i("RecursiveFileObserver", "MOVED_TO: " + path); break; case FileObserver.OPEN: Log.i("RecursiveFileObserver", "OPEN: " + path); break; default: Log.i("RecursiveFileObserver", "DEFAULT(" + event + " : " + path); break; } } /** * Monitor single directory and dispatch all events to its parent, with full * path. */ class SingleFileObserver extends FileObserver { String mPath; public SingleFileObserver(String path) { this(path, ALL_EVENTS); mPath = path; } public SingleFileObserver(String path, int mask) { super(path, mask); mPath = path; } @Override public void onEvent(int event, String path) { String newPath = mPath + "/" + path; MultiFileObserver.this.onEvent(event, newPath); } } }

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册