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);

    }

2014年11月24日月曜日

[日本旅遊]2014-08-20 函館-小幌-余市-比羅夫(3)

行程提要:
11:35-11:53  小幌--長万部
12:10-14:57  長万部--余市
17:17-18:13  余市--比羅夫


結束了鐵道與大自然的探險之後,又回到長万部。有17分鐘的停留時間,接著搭函館本線(山線)往小樽的車到余市。這時段剛好沒有特急可搭。反正余市特急也沒停,正合鐵道旅行的本意。

衝去看了一星壽司店,沒開。
衝去かにめし本店,賣光。Orz
結果這一天午餐沒得吃。只好繼續拿出非常食---ラーメンピー。
買了一大包吃到現在還沒吃光。

二股站。這時相機容量跟電量都不足了。Orz
阿花7不能邊充電邊拍,$ony該死。那是要怎麼長時間拍攝啊?

接下來直到有時間可以充電的時候就一直在持續的插拔usb。我想應該上千次有。
$ony該死。讓我意識到非買充電器跟預備電池不可了...
話說這候車室的側面設計還真像移動式廁所XD

蕨岱雙嬌


黑松內站。白樺櫸木自然生長的最北端地帶。


沿著山邊慢慢的往上爬。鐵路比道路高了一些。



熱郛站。

心中一直在唸ねぷねぷ...

鐵路越來越高。所以火車的速度也上不去。沿路平均時速大約60。



目名站。

木造車站,有下車狂拍一陣的衝動... 但是一下車的話行程就毀了...

蘭越站。

昆布站。為什麼昆布會在山裡面!?




ニセコ站。站名沒有漢字。

這裡有知名的國際滑雪場。

小型水力發電廠。


路過今天的住宿點:比羅夫站。先暫拍個幾張。

令人期待的睡車站。

河流也越來越湍急。

俱知安站。很特別,蠻好記的。在這裡停留約20分。

北海道富士---羊蹄山就在照片的左側。明天再拍... 因為容量跟電量不足。

有幾個站有留存這個以前用來通知旅客火車到站的鐘。

北側。

南側。

站前。

老式站牌。

車站本體。



車站前有泉水可以喝。有不少人拿空瓶裝水。我也試著裝了一點,的確是蠻好喝的水。

休息一陣子之後繼續出發。
小沢站。

小沢站出發之後,車速明顯下滑到30~40Km/h之間。車內也可以明顯感覺到傾斜,
看來相當陡。

銀山站。函館本線最高站。



可以看到還蠻廣的地方。

開始下山。

然別站。

越來越接近余市,農園也越來越多。

仁木站。




余市站。後面的建築物感覺蠻高級的。

南側。

北側。

水果也很重要,不過這次的重點是威士忌!

一出站就可以看到酒廠的建築。




酒廠正門。固定時間有解說員導遊,是在門口集合。不過時間不足,就自己逛了。

進門後看到的景色。

上圖的右側。


很貼心的設置公用雲台給大家拍紀念照。


解說威士忌的製作過程的告示牌。

沿著門口的路進來,各建築剛好分佈在兩側。

往回看入口。


乾燥棟。可以聞到濃濃的麥香跟燒炭味。

泥煤是影響威士忌風味的一大因素。




蒸餾棟。余市是唯一還在使用石炭燒火蒸餾的酒廠。
所以炭燒味更濃烈。

可惜這時間點是停工狀態。

石炭的爐門。用鏟子一鏟一鏟的放進去。地上也有不小心掉出來的石炭的燒焦痕跡。

牆上的威士忌製作流程介紹。各步驟所製作出來的液體的顏色不一樣。

添加石炭的爐門。

蒸餾完之後就是裝桶。忘記拍建物的外觀了。



橡木桶的製作流程。先把原木裁成8等分製作桶子的側板。

然後組起來。

側邊組完之後再製作上下板。

把橡木桶內部烤一烤。

有縫隙的地方就塞木屑。木屑浸在酒液裡面會膨脹,達到防漏的目的。

完成!

製造橡木桶的工具。

不用的橡木桶回收木製作的環保物品。

混合桶。

應該是把乾燥後的麥倒進去粉碎的機構。


發酵槽。


糖化與發酵的流程。

糖化發酵棟旁邊的通路。



以前的事務所。

全日本第一座威士忌酒廠。永世流傳。


內部。應該是社長室... 還有一個大掛鐘。

放滿了各年代的產品。可惜不能進去坐坐。

金庫也在這裡。

蒸餾棟的外觀。

リタハウス。竹鶴先生跟妻子兩人的住所。


過了舊事務所之後就是一個大的中庭。來兩張廣角照。


從中庭往回看。


發酵棟的外觀。相當大,可見這個步驟的產量。

中庭繞個一圈拍拍。從右側開始。


中庭的後段。
 

從中庭右後側往回拍。


這個塔不知道作用...

從中庭後方正中央往回拍。





餐廳很重要的。有好的餐點才有體力工作。


繼續前進之前回頭來一張。

接著往中庭的左側前進。



リタハウス的正門。


首先是玄關。換鞋的地方,入內參觀需要換室內鞋。土足嚴禁。



屋內播放著リタ小姐錄製的鋼琴曲。跟著樂聲一起體驗當時的生活氣氛。



屋內有模型介紹整個リタハウス的規格。只開放玄關的部份,有點可惜。不過要把屋內整理成可以參觀可能也有執行上的困難,就不用太打攪兩位了。


竹鶴政孝先生的銅像。




走過中庭之後,接著是最後的一個階段---儲藏。烈酒放越久就越甘醇,時間可以增加烈酒的價值。

最後是公司的博物館跟試飲區。


停車場。開車的人通常是從這邊入園。


還有販賣所。先進去博物館跟試飲區看看。


博物館一樓一進門就有一個小型的蒸餾器。


介紹世界各地的威士忌跟製造方法。

酒精濃度測量器。

當時竹鶴先生出國留學時期的物品。



1934年時代的產品。當時還是靠賣果汁的收入來養酒的虧損。

當時的威士忌現在已經是非賣品了吧...

各個時期的產品。

當時的出貨情景。人工把橡木桶滾到馬車上,運到小樽上船送到全國各地。











博物館逛完之後就是有料吧台。


也擺了許多其他地方的威士忌。

既然來這裡,當然重點是本家產品嘍。


因為原酒賣光了(真的是重傷),只好在付費吧台花錢買一杯喝看看。
濃濃的炭燒味,帶有香蕉跟極微的鹹味。有點辣,但是沒有10年酒的刺鼻感,相當順。
買不到啊~~~

接著上二樓,這邊有無料試飲區。一人限一杯威士忌,果汁無限(大家的重點也是放在威士忌上面XD)。左邊還有一台氣泡水機讓你可以做high ball。

享受完之後準備離開,這邊也擺了很多近期的產品。


然後看了一下手錶... 1650了。Orz
這段時間因為距離發車時間1717所剩不多,沒有拍照片的時間。
趕快衝過去賣店,原酒只剩下ピーティ&ソルティ的小瓶(180ml),總之買一瓶來試試。
然後就往回走,離開蒸餾廠。此時已經過了1700,門口的寄放區竟然鎖了... Orz
還好大門旁的事務所的人還在,不過領行李也還是耽擱了點時間。

1710左右到余市車站。這時還不給入場。只好在剪票口等待。

這件事影響JR北海道很大。

在天橋上等待列車到來。

來了。準備上車。

繼續往回搭到今天的住宿地點--車站旅館比羅夫。 車上滿滿的學生...

回程因為照片都拍過了,加上車上人太多所以不太想開相機。到了目的地再開始拍。

目送列車離開。

這,就是今天晚上的住宿地點。「車站旅館比羅夫」

旅館的主人相當有接待外國旅客的經驗。會簡單的英文,為了不增加老闆的負擔就直接請老闆說日文了。旅館比羅夫原本就如上圖,是一個原本為車站建物的旅館。以前因為距離有著北海道富士之譽的羊蹄山的登山口最近,還有不少的登山客撐住車站人流,所以車站建物意外的還不小。不過後來還是決定改成無人站。因緣際會之下,初代旅館老闆承租下來整個建物改造成旅館,「車站旅館比羅夫」正式誕生。也因為建物的產權還是屬於JR,名義上可以算是車站。
「可以合法睡在車站」,「可以在月台上烤肉進餐」,「可以在月台上洗澡」成了對鐵道迷的幾大賣點。

旅館有三種房間:單人的上下舖,3人的和式房,5人跟12人的大木屋。
一個人當然是上下舖嘍。

今天剛好住宿的旅客不多,可以獨占一個區塊。老闆說上下層可以自選。
雖然上層的高度比較夠,不過懶得爬上去所以還是下層就好...


 從車站內二樓往外看出去的感覺。

行李安頓好之後,開始了解環境。二樓走廊。右邊就是三人房的門口。

樓梯。牆上擺的好像是雪鞋。還有免費的wifi可用,真的是幫了大忙。

共用廚房。在這裡可以自己料理。不過附近沒有賣店...

也擺了很多之前留下來的東西。整個旅館的乾淨程度是完全沒有問題的。
洗手間幾乎跟新的一樣。還有些防凍對策的設施(例如給馬桶用的熱水蓮蓬頭),讓我這個熱帶民感到很新奇。下圖的爐子也是。

談話桌。

飲料的價格真的是佛心來的。可樂也是100日幣,比販賣機還便宜。(附近沒有販賣機)
而且老闆還說放在冷凍室的袋裝冰塊可以用。老闆不在的時候直接把錢投入存錢桶。

旅館正門一瞥。作為候車室的功能還是存在。不是房客也可以買飲料,相當佛心。

原本有養貓,不過今年4月的時候過世了。門的右下角有放著兩張照片跟一束花。


1800左右老闆就把晚餐準備好了。在月台上開始烤肉晚餐。一份¥1500。訂餐時有要求把羊肉換成牛肉,+500。
可以在月台上吃飯的體驗:無價。

還附送兩大顆烤飯糰。看來會吃到撐...

跟三位房客一起進餐。一組兩人,一組一人。感覺上年紀都比我大個1輪。
一位比較早休息所以比較沒聽到什麼訊息。
有一位有來台灣出差過。覺得長壽好重。可能平常是抽M7吧。 XD
另一位是來爬羊蹄山的。看來早上可能要早早出發。
聽到我是從台灣來,而且是來這裡只是為要來睡車站,一臉苦笑... XDDD

房客拿的相機,一台是砲坑1D3+大一元,一台是泥坑加紅環,退休人士果然很有錢...

進餐到一半有列車停靠,聽說只要看車上的人的表情就知道是不是當地人。XD

因為我吃得比較慢,加上爐火跟鐵網有點距離,其實烤不快。吃了一個小時半。
還有飯後水果:哈密瓜跟葡萄。真的是吃到撐...


吃飽飯之後準備洗澡。

傳說中的巨木浴缸。比動畫裡面露營用的鐵桶浴缸要更有大自然的氣氛。XD
還有加熱器可以再加溫。我按了5分鐘,有點過熱... 3分鐘就很夠了。
給他用力泡了半小時,屋頂還特別留一大片空間可以仰望星空,因此差點睡著...


新奇的體驗就在蟲鳴聲的陪伴下,加上令人安心的居家感,2200後一覺安眠到天亮。
記得一定要蓋被。夏天的北海道,凌晨的溫度也會掉到20左右...