Android

Java安卓学习总结(二十一)

第十七章 双版面主从用户界面

有关别名资源

别名资源时一种指向其它资源的特殊资源,存放在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中的,同名可能会发生冲突,所以另外写了一个名字。

GIF 2020 6 23 20 53 34

发表评论