2014年12月9日火曜日

[安藤]android FragmentTabHost + Fragment + FragmentManager 實作Fragment版的每個Tab都有自己的history

這次真的花太久時間在解決這個方案了。

起因是因為TabActivity被Google死亡宣告(deprecated)。
加上客戶的無理要求「在所有的頁面都要有側滑頁」。

首先使用FragmentTabHost,因為跟之前的TabHost的程式碼接近,稍作修改就可以實做出5個Fragment。
但是tab1->tab1-2->tab2->back鍵->回到tab1,發現還是停留在tab1-2並不會回去tab1。很明顯的是FragmentManager的backstack沒辦法處理。

只好硬著頭皮再找其他方案。簡單搜尋了一下,發現android OS 4.2之後出現getChildFragmentManager(),
似乎是可以做到讓fragment裡面還可以有自已的History。於是朝這個方向嘗試。

找到了一篇介紹這類的方法的網頁,裡面使用tabhost跟ontabchanged listener去處理。 本篇就是建構此文之上去做的改善。


設計概念:

  • 使用一個FragmenActivity。加上FragmentTabHost讓此Activity可以使用tab 來做 fragment的切換。
    在FragmenActivity上面建立N個tab在android.R.id.tabcontent上面操作的fragment。
  • 這N個fragment都使用一個設計好的空fragment class建立。
    並在此空的N個fragment上面,利用android os 4.2版之後新增的getChildFragmentManager()函式,可以分別取得此N個空fragment的FragmentManager,fragment Transaction history功能即可依tab來分別。

    (空fragment class的特性:layout xml只有一個給ChildFragmentManager利用的framelayout,另外加上使用ChildFragmentManager來做回上一層fragment的功能給FragmenActivity取用。)
  • 各個空fragment直接進行第一次的fragment Transaction。如此就不會出現空白畫面。
    要繼續往下進行fragment Transaction的話,使用getSupportFragmentManager()就可以拿到該層的FragmentManager。(也就是空fragment class的FragmentManager。)




注意下面幾點:

  • 此文裡面浪費了一個framelayout給Tabhost使用,本方法改善此設計。
  • 截自android support library 20版為止,android.support.v13.app的fragment不支援getChildFragmentManager()。
  • 有在fragment的onCreateView()保存之前已經inflate好的fragment layout的習慣的話,記得一定要在第二次執行oncreateview()之後對parentview去做clearallview()的動作。(因為原本的layout已經被attach到fragment container之後,執行fragment的re-attach/replace等等動作的時候會因為原本的view已經被attach,有parent了,導致Exception發生。)或是改成每次onCreateView()都重新inflate layout。
    這個問題卡了我近一天... 因為Exception完全沒有提示正確的問題點...
  • getChildFragmentManager(),操作的fragment container為此fragment inflate的layout裡面存在的某一個layout。(不是getSupportFragmentManager()的那個)
  • 假如你還想在這樣的架構之下加用ViewPager + FragmentPagerAdapter的話,請使用「FragmentStatePagerAdapter」。它會幫你處理切換tab時所需要處理的fragment load/unload 問題。



系統架構:

MainActivity:
 
public class MainActivity extends FragmentActivity implements View.OnClickListener, TabHost.OnTabChangeListener
{
 public static final String ROOT_FRAGMENT_ARGS = "tabrootFragment";
 private boolean bBackPressed = false;
 private FragmentTabHost tabHost;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mainlayout);

        tbWidget = (TabWidget)findViewById(android.R.id.tabs);
                
        tabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
        tabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);
        tabHost.setOnTabChangedListener(this);

        addTab("tab1", "Tab1", Fragment1.class);
        addTab("tab2", "Tab2", Fragment1.class);
        addTab("tab3", "Tab3", Fragment1.class);

    }

    private void addTab(String tabId, String indicactor, Class fragmentClass, Bundle args) 
    {
     if (args == null)
      args = new Bundle();
        args.putString(ROOT_FRAGMENT_ARGS, fragmentClass.getName());

        tabHost.addTab(tabHost.newTabSpec(tabId).setIndicator(indicactor), EmptyFragment.class, args);
    }
}

    public EmptyFragment getCurrentFragment() {
        return (EmptyFragment) getSupportFragmentManager().findFragmentById(android.R.id.tabcontent);
    }

 @Override
 public void onBackPressed()
 {
  EmptyFragment rootFragment = getCurrentFragment();
        if (rootFragment == null || !rootFragment.popBackStack())
  {
   if (bBackPressed)
   {
    finish();
   }
   else
   {
       bBackPressed = true;
       mHandler.postDelayed(new Runnable(){
       public void run() {
        bBackPressed = false;  
       }
      }, 3000);
    Utils.showToast(this, "Press Back twice to exit app");
   }
  }

 }

EmptyFragment:
 
public class EmptyFragment extends Fragment
{
    private View contentView = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
     super.onCreateView(inflater, container, savedInstanceState);

     if (contentView == null)
      contentView = inflater.inflate(R.layout.emptyfragment, container, false);
     else
      ((FrameLayout)contentView.getParent()).removeAllViews();  //prevent fragment history return child attached exception.
        return contentView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        initFragment();
    }

    /**
     * Pop fragment backstack.
     * @return return true if exists backstack
     */
    public boolean popBackStack() {
        return getChildFragmentManager().popBackStackImmediate();
    }

    /**
     * Initialize Fragment.
     */
    private void initFragment() {

        Bundle args = getArguments();
        String rootClass = args.getString(MainActivity.ROOT_FRAGMENT_ARGS);

        FragmentManager fragmentManager = getChildFragmentManager();
        if (fragmentManager.findFragmentById(R.id.fragment) != null) {
            return;
        }

        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        Fragment fragment = Fragment.instantiate(getActivity(), rootClass, args);
        fragmentTransaction.replace(R.id.fragment, fragment);

        fragmentTransaction.commit();
    }

    /**
     * Return Fragment in displayed
     * @return Fragment
     */
    Fragment getCurrentFragment() {
        return getChildFragmentManager().findFragmentById(R.id.fragment);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Call child fragment method
        getCurrentFragment().onActivityResult(requestCode, resultCode, data);
    }

}


R.layout.mainlayout:
 
 
           

                

                
            


R.layout.emptyfragment:
 

2014年12月8日月曜日

[安藤]android GridLayout + image view 等分/均分



GridLayout 可以方便的拿來作方格狀的佈局。但是... GridLayout本身的margin卻無法自動等分。只好自己diy。
使用限制:
grid裡面的view假設都是相同大小。
    int columnCount = 3; //看想要橫排幾個都可以。
    gl = new GridLayout(context);
    contentView = gl;
    gl.setColumnCount(columnCount);
    gl.setOrientation(GridLayout.HORIZONTAL);
    gl.setUseDefaultMargins(false);
    gl.setBackgroundResource(R.color.white);

    int imageViewMargin = 50;  //隨你高興要讓等分的間隔多寬。
    for (int i = 0; i < [items]; i++)  //item數量可變
    {
        ImageView iv = new ImageView(context);
        GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
        lp.width = Double.valueOf((res.getDisplayMetrics().widthPixels - (imageViewMargin * (columnCount + 1))) / columnCount).intValue();
        lp.height = lp.width;
        lp.setMargins(imageViewMargin, imageViewMargin, 0, 0);  //只設左跟上方的margin即可
       
        iv.setLayoutParams(lp);          
        gl.addView(iv);

    }