第十七章 双版面主从用户界面
有关别名资源
别名资源时一种指向其它资源的特殊资源,存放在res/values中
<item name="activity_masterdetail" type="layout">@layout/activity_fragment</item>
调用时使用这个别名资源的名字,但是使用的方式完全按照其type指定的方式,使用的实际内容是引用的内容。
为平板设备创建的专用可用资源,是与之前的别名资源文件名、目录完全相同的资源,唯一的区别在于有最小的屏幕宽度限制和引用的视图文件不同。
//w600dp <item name="activity_masterdetail" type="layout">@layout/activity_twopane</item>
android会根据设备屏幕大小自动判断使用的可选资源。
有关fragment回调接口
这个问题产生于一个activity下同时托管了两个fragment,由其中一个fragment来启动另一个fragment时需要将后者打入fragmentManager中。由于这个操作的环境是在fragment中,所以需要首先找到托管其的activity,再利用activity的fragmentManager来启动另一个fragment。
由于这两个fragment被同一个activity托管,实际上他们都可以由activity直接管理,如果要经由另一个fragment才能启动的话,限制了fragment的独立性。
此时应当使用回调接口,其做法是将activity启动一个fragment的功能(或者其它需要利用activity实现的)在一个接口中实现,那么在fragment中就可以实例化出该接口,使其赋值为托管的activity,就可以用这个回调接口的实例去完成fragment的处理任务。
private Callbacks mCallbacks; public interface Callbacks { void onCrimeSelected(Crime crime); void onCrimeDeleteDirectly(Crime crime); } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCallbacks=(Callbacks)activity; } @Override public void onDetach() { super.onDetach(); mCallbacks=null; }
以上是接口的创建和实例化,书上说最好使用public void onAttach(Context context),但是我查了资料发现我用的API22恰好就没有context,且context版的在API23以下的设备上运行不成功,所以我就直接使用onAttach(Activity activity)。
在CrimeListActivity中实现该接口后重写void onCrimeSelected(Crime crime)方法以实现根据所选的Crime和设备屏幕大小来创建适当的视图。
@Override
public void onCrimeSelected(Crime crime) {
if (findViewById(R.id.detail_fragment_container)==null)
{
Intent intent=CrimePagerActivity.newIntent(this,crime.getId());
startActivity(intent);
}
else
{
Fragment newDetail=CrimeFragment.newInstance(crime.getId());
getSupportFragmentManager().beginTransaction().replace(R.id.detail_fragment_container,newDetail).commit();
}
}
那么在需要创建CrimeFragment的地方只要写一句
mCallbacks.onCrimeSelected(crime);
就可以了。
此外由于布局的变化导致了一些其它的问题,也可以使用回调接口来实现,例如需要在更改锅CrimeFragment的内容之后就需要立即刷新左侧的RecyclerView,这时候就可以让接口实例来完成刷新任务,实现方法是找到包含RecyclerView的fragment,调用其updateUI方法。这里寻找栈中的fragment是根据视图资源的id来寻找的。
@Override
public void onCrimeUpdate(Crime crime) {
CrimeListFragment listFragment=(CrimeListFragment)getSupportFragmentManager().findFragmentById(R.id.fragment_container);
listFragment.updateUI();
}
之前在按下一个Crime Fragment的删除键之后,直接调用了托管activity的finish()方法,但是现在CrimeFragment是由主activity直接托管的,所以需要更改逻辑。这里同样是使用回调接口:
@Override public void onCrimeDelete(Crime crime) { CrimeFragment crimeFragment=(CrimeFragment)getSupportFragmentManager().findFragmentById(R.id.detail_fragment_container); if (crimeFragment!=null) { getSupportFragmentManager().beginTransaction().remove(crimeFragment).commit(); } }
其中要判断获取的fragment是不是为null,如果remove的参数是null会报错:
NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim
这个方法是实现了删去一个Crime之后将其原来的CrimeFragment也清空,同样的左侧的记录也要被删除,所以需要调用一次mCrime。
//CrimeFragment public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.delete_crime: CrimeLab crimeLab=CrimeLab.get(getActivity()); crimeLab.deleteCrime(mCrime); //getActivity().finish(); mCallbacks.onCrimeUpdate(mCrime); mCallbacks.onCrimeDelete(mCrime); return true; default: return super.onOptionsItemSelected(item); } }
有关挑战练习 添加滑动删除功能
实现滑动功能
创建一个类继承ItemTouchHelper.Callback,并且与RecycleView建立链接
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback
{
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(0, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mCallbacks.onCrimeDeleteDirectly(((Holder)viewHolder).getCrime());
CrimeLab crimeLab=CrimeLab.get(getActivity());
crimeLab.deleteCrime(((Holder)viewHolder).getCrime());
mAdapter.onItemDelete(viewHolder.getAdapterPosition());
updateUI();
}
}
其中第一个方法是确定可以移动的方向:上下为一组,左右为一组,按照定义的类型作为返回值;第二个方式是上下拖动时的处理;第三个是左右滑动时的处理。
实现删除功能
需要定义一个接口来实现删除功能,
public interface ItemHelper {
void onItemDelete(int position);
}
让Adapter来实现这个接口
@Override
public void onItemDelete(int position) {
mCrimes.remove(position);
notifyItemRemoved(position);
}
用position作为参数是因为position是可以在Adapter和ItemTouchHelper.Callback中都能得到的且唯一确定需要删除的crime的位置的元素。
所以问题就在于如何获取这个位置上的crime。我是在viewHolder中定义了一个getter,然后根据viewHolder来获取其绑定的crime。
private abstract class Holder extends RecyclerView.ViewHolder { private Crime mCrime; public Crime getCrime() { return mCrime; } ... }
所以public void onSwiped中的具体实现是
@Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { mCallbacks.onCrimeDeleteDirectly(((Holder)viewHolder).getCrime()); CrimeLab crimeLab=CrimeLab.get(getActivity()); crimeLab.deleteCrime(((Holder)viewHolder).getCrime()); mAdapter.onItemDelete(viewHolder.getAdapterPosition()); updateUI(); }
其中(Holder)viewHolder).getCrime()是由于历史原因要实现的多态,mCallbacks.onCrimeDeleteDirectly的代码和mCallbacks.onCrimeDelete相同,只不过一个是CrimeListFragment中接口的方法,还有一个是CrimeFragment中的,同名可能会发生冲突,所以另外写了一个名字。