2018年6月3日 星期日

[Android Architecture Component]Navigation Fragments

簡介

從Android Studio 3.2開始可以使用Navigation Component(2018/06/04 目前需要使用Canary版本)
這個元件可以簡化程序之間跳轉的呈現,並且有可視的UI可以看到流程
從Android Developer上也可以查看更多介紹 Link



規則

使用這個元件有一些Goodle給的建議規範:
  • The app should have a fixed starting destination
   APP應該給予固定的起始點
  • A stack is used to represent the navigation state of an app
   Navigation是用Stack的方式在導航流程,而且是以"後進先出"的方式堆疊,也就是最後呈現的頁面是在堆疊的最上層(頂部)
  • The Up button never exits your app
   如果使用者目前在起始頁(第一頁),那麼當使用者點擊Back時,不應該退出APP,而是應將使用者導至其他應用頁面
  • Up and Back are equivalent within your app’s task
   同上,點擊Back不應退出APP
  • Deep linking to a destination or navigating to the same destination should yield the same stack
無論使用者從什麼地方進入,都應在相同的堆疊上

Getting Started 開始使用

在Gradle上加入相依:

 
dependencies {

    def nav_version = "1.0.0-alpha01"

    implementation "android.arch.navigation:navigation-fragment:$nav_version" // use -ktx for Kotlin

    implementation "android.arch.navigation:navigation-ui:$nav_version" // use -ktx for Kotlin

}

然後新增一個Navition res檔
對res資料夾右鍵 -> Android Resource File -> Resource Type 選Navition




認識編輯器


  1. 1.Destinations list - 列出在圖形編輯器中的所有目的地。
  2. 2.Graph Editor - Destinations的可視化。
  3. 3.The Attributes Editor - 包含Destinations和action相關的屬性。

建立目的地


點選圖中的圈起來的+,此時若有Fragemnt他就會顯示目前有的,這裡我已經先建立好兩個Fragment(MainFragment、ListFragment)

好了之後我們就可以將他們連結起來

連結後我們就可以準備使用它了

使用

切換至你想要用的Acitvity xml ,加入一個Fragment 元件,如下:
 

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/nav_graph"
        app:defaultNavHost="true"
        />

</android.support.constraint.ConstraintLayout>


其中有兩個重點

  •  app:navGraph="@navigation/nav_graph"
   修改為剛剛創建的Navigation Resource檔案
  •  app:defaultNavHost="true"
   讓你的NavHostFragment可以攔截系統Back事件,你也可以改寫AppCompatActivity.onSupportNavigateUp() 然後使用 NavController.navigateUp 

跳轉Fragment

要切換Fragment是使用NavController 這個Class,有以下幾種方法:

  • NavHostFragment.findNavController(Fragment)
  • Navigation.findNavController(Activity, @IdRes int viewId)
  • Navigation.findNavController(View)

簡單來說可以這樣使用:

viewTransactionsButton.setOnClickListener { view ->


   view.findNavController().navigate(R.id.viewTransactionsAction)

}
以上大致上就是整個流程,也可以上我的GitHub上看範例 有問題或有錯誤歡迎留言討論討論~~

2017年7月18日 星期二

[Android Kotlin]Kotlin與ButterKnife並存使用


由於Kotlin成為Android新一代的開發語言,所以最近也開始使用了Kotlin

然而在專案中常常會用到有名的奶油刀(ButterKnife)來幫助我們bind view

雖然在kotlin當中我們不需要再藉由ButterKnife來Bind view,
因為只要在專案中的gradle加上
 
apply plugin: 'kotlin-android-extensions'

Kotlin就可以自動幫我們從xml裡面找到view並且bind.

既然如此,那為何我還需要用到ButterKnife呢?

因為雖然Kotlin可以幫助我們BindView,但是ButterKnife除了BindView之外,他還有很多好用的Annotation可以使用

像是
 
    @OnClick(R.id.btn)
    fun onClick(view: View) {
        //do something
    }

這類好用的Annotation來幫助我們,但如果你使用了Kotlin之後,你會發現編譯通過,但ButterKnife似乎沒有作用了?

沒錯,因為Kotlin要用kapt,而不是原本的apt
所以我們需要在gradle中將ButterKnife改為
 
kapt {
    generateStubs = true
}
dependencies {
    compile("com.jakewharton:butterknife:$butterknife_version") {
        exclude group: 'com.android.support', module: 'support-annotations'
        exclude group: 'com.android.support', module: 'support-compat'
    }
    kapt "com.jakewharton:butterknife-compiler:$butterknife_version"
}


其中特別注意到butterknife_version需要一致,否則就會出問題哦!

2017年3月28日 星期二

[Android]如何使用Viewpager+Tablayout

雖然Android 的Viewpager 和Tablayout網路上的教學文章非常多

但還是常常看到有新入門的朋友不是很清楚該怎麼將這兩個常用的元件組合使用,又加上看到幾篇教學文章的寫法並不是很清楚,所以就決定自己來寫一篇拉!

如果你還不清楚Viewpager和Tablayout是什麼的話,建議可以先看一下官方的說明:

Viewpager -


Tablayout -


這邊先簡單說一下Viewpager
Viewpager就是App裡面你常常可以看到可以左右滑動的元件,通常可以被應用在廣告輪播、App起始介紹頁、還有常常看照片時左右滑動的照片頁,反正只要看到可以左右滑動的,那麼有很大的機率都是Viewpager哦!

至於TabLayout則是你可以想像你在左右滑動Viewpager時,上面有一條線會跟著動,滑到哪線就跟到哪,Android裡稱它為Indicator

再來就進入主題,該如何將Viewpager和Tablayout合在一起使用,這邊就不介紹該怎麼使用Viewpager了,如果有需要,以後再寫一篇~


首先需要將Tablayout的dependencies加進App gradle裡面:

dependencies { 
      compile 'com.android.support:design:25.2.0' 
}

這邊我放的版本是25.2.0,但這不是最新版本,如果想用新版本可以到這裡查看最新版本:

加上dependencies後就可以到xml將TabLayout與Viewpager放上去拉



<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="?android:actionBarSize"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/tablayout"/>

</RelativeLayout>


Layout弄好之後就可以開始寫Code的部分

其實步驟也很簡單只有幾個步驟

1.findview,很簡單這沒問題

viewPager = (ViewPager) findViewById(R.id.viewpager);
tabLayout = (TabLayout) findViewById(R.id.tablayout);

2.初始化viewpager,生成一個adapter,把它餵給viewpager

viewPager.setAdapter(new SimplePagerAdapter());

3.結合viewpage 和tablayou(重點)
這邊你可能會看到網路上文章是給tabLayout一個listener,然後再丟給viewpager,但其實只要用下面這行就好...

tabLayout.setupWithViewPager(viewPager);

4.給Tab名稱(重要)
你可能在網路上Google或官方文件看到說用下面這種方式

tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));

tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));

tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));


但其實只要在viewpager的Adapter裡面override getPageTitle就可以了,像這樣:

@Override
public CharSequence getPageTitle(int position) {
    return "Tab " + pages.get(position);
}

5.結束收工,沒錯!就這麼簡單!
全部程式碼
public class MainActivity extends AppCompatActivity {

    private ViewPager viewPager;

    private TabLayout tabLayout;

    private ArrayList<Integer> pages;





    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        initData();

        initViews();

    }



    private void initData() {

        pages = new ArrayList<>();

        for (int i = 0; i < 5; i++) {

            pages.add(i);

        }

    }



    private void initViews() {

        viewPager = (ViewPager) findViewById(R.id.viewpager);

        tabLayout = (TabLayout) findViewById(R.id.tablayout);

        viewPager.setAdapter(new SimplePagerAdapter());

        tabLayout.setupWithViewPager(viewPager);

    }



    private class SimplePagerAdapter extends PagerAdapter {



        @Override

        public int getCount() {

            return pages.size();

        }



        @Override

        public boolean isViewFromObject(View view, Object o) {

            return o == view;

        }



        @Override

        public Object instantiateItem(ViewGroup container, int position) {

            LinearLayout ll = new LinearLayout(container.getContext());

            ll.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

            ll.setGravity(Gravity.CENTER);

            TextView tv = new TextView(container.getContext());

            tv.setTextColor(Color.YELLOW);

            tv.setText("current page is=" + pages.get(position));

            ll.addView(tv);

            container.addView(ll);

            return ll;

        }



        @Override

        public void destroyItem(ViewGroup container, int position, Object object) {

            container.removeView((LinearLayout) object);

        }



        @Override

        public CharSequence getPageTitle(int position) {

            return "Tab " + pages.get(position);

        }

    }

}


其實這個組合目前很常用到,建議一定要學會哦!
有問題或者有錯的歡迎留言討論唷

最後附上程式碼

GitHub



2016年7月20日 星期三

[Android]時間倒數countDownTimer

今天在開發時,需要製作一個倒數的功能
在以前開發時我總是使用Timer or Runnable去手動開發倒數功能


結果今天居然讓我意外發現了原本Android就有個類別是倒數功能

就是--
CountDownTimer 

而且這個class還是android api 1就有了...身為android開發者還真是慚愧居然不知道這個類別

使用方法大致是這樣(官方範例)

new CountDownTimer(30000, 1000) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
  }.start();


參數也非常簡單

CountDownTimer(long millisInFuture, long countDownInterval)


millisInFuture-總共倒數時間

countDownInterval-倒數間隔時間


然後實作兩個方法
onTick -倒數時做的事情
onFinish-倒數結束時的事情


是不是很方便阿~

最後附上官方說明:
https://developer.android.com/reference/android/os/CountDownTimer.html

2016年7月12日 星期二

[Android]Google play 上架,應用程式更新遭到拒絕

今天在上架Android apk新版本時候遇到了

應用程式更新遭到拒絕:
您的 APK 含有安全性漏洞,此舉違反惡意行為政策,因此遭到拒絕。如果您提交的是更新版本,那麼前一個應用程式版本仍會保留在 Google Play 商店中。


上架過這麼多android app了,還是第一次遇到被退貨的apk...

在經過查詢之後才發現,原來是之前webview 忽略 ssl ,被google說是安全性漏洞了,那裡的確是為了測試方便,不過居然被退貨還真是神奇(?)

後來將webview的這裡:

    @Override
      @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 handler.proceed();
            }


拿掉之後,再重新送審

果然就成功上架了....沒想到google 還真的是有在審查app呀..


2016年7月8日 星期五

Android Fragment in Viewpager 避免初始化 onResume

在開發Android 的過程中,常常會使用到viewpager來幫助開發

但有時候又希望viewpager 裡面的 fragment 不要在一開始setAdapter時就加載。

因為如果fragment裡面,我們做了許多的網路API request,或是圖片的加載,那麼如果fragment的數量多,就會讓使用者等很久

所以在拜過Google 大神後,發現到 viewpager 會手動調用 fragment裡面的public void setUserVisibleHint(boolean isVisibleToUser) 方法,

這個方法很好理解,
就是當我們滑動到某一個fragment,或是 viewPager.setCurrentItem(n)時,就會調用到這個方法

所以我們可以將這個方法看成:

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            // fragment onResume
        } else {
            // fragment onPause
        }
    }


這樣是不是很簡單呢~~

2016年5月6日 星期五

[Android]Google Analytics 8.3.0 conflict 版本衝突

昨天在公司幫忙將專案加入GA時
我照著Google 官方說明一步一步加上去時 官方範例點我
發現怎麼加上dependencies後

compile 'com.google.android.gms:play-services-analytics:8.4.0'

在compile 整個project 時,總是會出現錯誤訊息

Please fix the version conflict either by updating the version of the google-services plugin.

後來搞了老半天,終於發現也有人有相同的問題

起初我一直以為是我的其他lib裡面的google service 版本與8.4.0版的analytics衝突了,結果原來問題在於需要把

apply plugin: 'com.google.gms.google-services'

擺到整個build.gradle檔案的最下方,這時才可以正常compile
整個是很無言阿,在終於加入GA後,另一個問題又來了

當我trackScreen之後,我查看logcat是否有正常將訊息回傳至Google service上,結果居然跟我說

W/GAv4(10565): AnalyticsReceiver is not registered or is disabled. Register the receiver for reliable dispatching on non-Google Play devices. See http://goo.gl/8Rd3yj for instructions.

W/GAv4(10565): AnalyticsService not registered in the app manifest. Hits might not be delivered reliably. See http://goo.gl/8Rd3yj for instructions.

什麼阿!!這到底是什麼鬼..以前加GA不是很簡單嗎?
只要google play service的jar檔有加到lib當中,在將key丟入code中不是就可以用了?後來還多了一個需要放入configuration file,這個又還好,因為官方上就有很明確的跟我說要放了

但以前都不用放receicer才對阿...什麼時候又改的
於是我又詢問了google ,才終於找到答案

現在必須要在AndroidManifest.xml當中加入

        
        
            
                
            
        
        

        
        
            
                
            
        
          

這才終於解決了加GA這件事,真是花了我不少時間阿...