Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Thursday, March 8, 2018

FCM個人化推播issue

今天(2018/03/09)同事分享來的一個issue,因為公司需要發個人化推播,他發現收到推播的機臺彼此在錯亂。
釐淸了一下,錯亂造成的原因是因為都是用同一組Android id。
所以我們假設FCM底層在判斷機臺的方式,有使用Android ID當唯一值。

註︰在GCM的年代,官方有明文顯示不建議商務需求使用個人化推播。

Tuesday, January 16, 2018

如何處理觸控事件

文章攢寫時間︰2018/01/16 11:54

本篇參考來源
1. STACKOVERFLOW

文章開始

觸控頁面與元件的關係
When a touch event occurs, first everyone is notified of the event, starting at the Activity and going all the way to the view on top.
Then everyone is given a chance to handle the event, starting with the view on top and going all the way back to the Activity. So the Activity is the first to hear of it and the last to be given a chance to handle it.
當一個觸控事件發生時,每個元件都會在第1時間被通知,從Activity層開始層層往上,通知到最頂層的View。
每個元件在這段被通知的過程中,都有機會來處理這個事件,Activity是第一個聆聽到事件的元件,但是它卻是在最後才被處理到的元件。

If the Activity or some ViewGroup wants to handle the touch event right away (and not give anyone else down the line a chance at it) then it can just return true in its onInterceptTouchEvent().
如果Activity或某些ViewGroup想要立即處理觸控事件(並且不給其它元件任何處理的機會),那麼可以在該元件的onInterceptTouchEvent()中返回true。

If a View (or a ViewGroup) has an OnTouchListener, then the touch event is handled by OnTouchListener.onTouch(). Otherwise it is handled by onTouchEvent(). If onTouchEvent() returns true for any touch event, then the handling stops there. No one else down the line gets a chance at it.
如果一個View(或ViewGroup)想要擁有OnTouchListener事件,基本上該觸控事件是由OnTouchListener.onTouch()來處理,否則則由onTouchEvent()事件來處理。如果onTouchEvent()針對每個觸控事件皆返回true,那麼事件將被處理到該階層而停止,後面剩餘的元件將無法得到任何觸控的機會。

-------------------------------
More detailed explanation
詳細解說

The above diagram makes things a little more simple than they actually are. For example, between the Activity and ViewGroup A (the root layout) there is also the Window and the DecorView. I left them out above because we generally don't have to interact with them. However, I will include them below. The description below follows a touch event through the source code. You can click a link to see the actual source code.
下面的圖例也許能把整件事變得稍微簡單些,舉例來說,在Activity與ViewGroup A(根佈局)之間還有Window與DecorView。我們把它們排除在上面,因為我們一般來說不會與這兩個階層互動。不過,我會在下面的範例中引入它們。下面的描述是觸控事件的程式碼,你可以點擊連結來看源始碼。

1.The Activity's dispatchTouchEvent() is notified of a touch event. The touch event is passed in as a MotionEvent, which contains the x,y coordinates, time, type of event, and other information.
Activity的dispatchTouchEvent()被通知有一個觸控事件,這個觸控事件藉由MotionEvent傳入,過程中包含了x、y的坐標值、時間。事件類型與其它的信息。

2.The touch event is sent to the Window's superDispatchTouchEvent(). Window is an abstract class. The actual implementation is PhoneWindow.
觸發事件發到superDispatchTouchEvent()到Window階層,Window是一件抽象類別,實際實作的是PhoneWindow。

3.The next in line to get the notification is DecorView's superDispatchTouchEvent(). DecorView is what handles the status bar, navigation bar, content area, etc. It is actually just a FrameLayout subclass, which is itself a subclass of ViewGroup.
接下來我們取到的通知是DecorView的superDispatchTouchEvent()。DecorView是處理上方狀態列(status bar) 、下方導覽列(navigation bar)以及內容...等等的東西。它實際上只是一個FrameLayout的子類別,它本身就是ViewGroup的子類。

4.The next one to get the notification (correct me if I'm wrong) is the content view of your activity. That is what you set as the root layout of your activity in xml when you create the layout in the Android Studio's Layout Editor. So whether you choose a RelativeLayout, a LinearLayout, or a ConstraintLayout, they are all subclasses of ViewGroup. And ViewGroup gets notified of the touch event in dispatchTouchEvent(). This is the ViewGroup A in my diagrams above.
下一個得到通知(如果我錯了請糾正我)的是你的Activity裡的content view。這個就是你在Android Studio佈局編輯器裡編輯的、你設為根佈局的layout xml。因此,無論你選擇竹旳是RelativeLayout、LinearLayout、或是ConstraintLayout,它們都隸屬於ViewGroup的子類別。ViewGroup在dispatchTouchEvent()中獲得到觸控事件。這是我上圖裡的ViewGroup A。

5.The ViewGroup will notify any children it has of the touch event, including any ViewGroup children. This is ViewGroup B in my diagrams above.
ViewGroup將會開始通知觸控事件至底下的任何子元件項目,這也包含了ViewGroup的所有子項。在圖例中我以ViewGoup B來做舉例。

6.Anywhere along the way, a ViewGroup can short-circuit the notification process by returning true for onInterceptTouchEvent().
在任何情況下,隸屬於ViewGroup的元件皆可透過onInterceptTouchEvent()回傳true來讓流程"短路"。

7.Assuming no ViewGroup cut the notifications short, the natural end of the line for the notifications is when the View's dispatchTouchEvent() get's called.
假設沒有ViewGroup讓通知流程縮短,那麼通知事件自然結束的點就是在View的dispatchTouchEvent()事件被調用的時候。

8.Now it is time, to start handling the events. If there is an OnTouchListener, then it gets the first chance at handling the touch event with onTouch(). Otherwise, the View's onTouchEvent() gets to handle it.
通知事件的流程到此結束,是時候開始往上處理觸控事件了。如果在View層有一個OnTouchListner,那麼它會在onTouch()中得到第1次的觸控處理的機會。否則會由View的onTouchEvent()來處理觸控事件。

9.Now all the ViewGroups recursively up the line get a chance to handle the touch event in the same way that View did. Although, I didn't indicate this in the diagram above, a ViewGroup is a View subclass, so everything I described about OnTouchListener.onTouch() and onTouchEvent() also applies to ViewGroups.
接下來所有的ViewGroups都像View層一樣,皆以遞迴的方式向上處理觸控事件。儘管我沒有在上圖指出ViewGroup是View的子類別,但我描述有關OnTouchListener.onTouch()和onTouchEvent()也皆適用於ViewGroups。

10.Finally, if no one else wanted it, the Activity also gets the last chance to handle the event with onTouchEvent().
1最後,如果沒有任何的元件需要它,Acitivy也會在onTouchEvent()中得到最後一次的機會來處理觸控事件。

FAQ
1.When would I ever need to override dispatchTouchEvent()?
我何時需要覆寫dispatchTouchEvent()?

答:
Probably you wouldn't need to, unless you need to do some extra routing that doesn't occur by default. To monitor touch event notifications, you can override onInterceptTouchEvent() instead.
也許你不需要這樣做,除非你需要做一些額外的路由,否則默認狀況下這個情況是不會發生的。要監控觸控事件,你可以覆寫onInterceptTouchEvent()即可。

2.When would I ever need to override onInterceptTouchEvent()?
我何時需要覆寫onInterceptTouchEvent()?

答:
If you just want to spy of the touch notifications that are coming in, you can do it here and return false.
如果你只是想監控正在進入的觸控事件,你可以在此時處理並返回false。

However, the main purpose of overriding this method is to let the ViewGroup handle a certain type of touch event while letting the child handle another type. For example, a ScrollView does this to handle scrolling while letting its child handle something like a Button click. Conversely, if the child view doesn't want to let its parent steal its touch event, it can call requestDisallowTouchIntercept().
但是,覆寫這個函式的主要目的是為了讓ViewGroup能處理特定類型的觸控事件,同時讓子元件處理其它類型的事件。舉例來說,ScrollView這樣子實作來處理滾動,同時它的子元件(像是Button)則是處理按鈕點擊事件。相反地,如果子視圖不想要讓父母竊取其觸控事件,則可以調用requestDisallowTouchIntercept()。

3.What are the touch event types?
觸控類型有哪些?

答:
The main ones are 主要的有
    ACTION_DOWN - This is the start of a touch event. You should always return true for the ACTION_DOWN event in onTouchEvent if you want to handle a touch event. Otherwise, you won't get any more events delivered to you.
    ACTION_DOWN - 這是觸控事件的開始,在onTouchEvent()裡ACTION_DOWN的時候你也許永遠要返回true。否則,你不會再收到任何更多的事件。
    ACTION_MOVE - This event is continuously fired as you move your finger across the screen.
    ACTION_MOVE - 當你的手指在螢幕上不斷的滑動時,該事件會不斷的被觸發。
    ACTION_UP - This is the last event of a touch event.
    ACTION_UP - 這是觸控事件的最後一個事件。

A runner up is ACTION_CANCEL. This gets called if a ViewGroup up the tree decides to intercept the touch event.
第二類型的是ACTION_CANCEL,如果ViewGroup樹狀上決定攔截觸控事件,則會收到該事件。

You can view the other kinds of MotionEvents here. Since Android is multi-touch, events are also fired when other fingers ("pointers") touch the screen.
你可以在這裡參考其它類型的MotionEvents,因為Android是多點觸控的,當其它手指("點擊")螢幕時,事件也會被觸發。

延伸閱讀

Tuesday, January 3, 2017

關於android:clipToPadding

文章攢寫時間︰2017/01/04 11:48

本篇參考來源
1. android:clipToPadding属性的分析——以ListView的"别样"padding为例
2. android:clipToPadding和android:clipChildren

本篇適合

1.Android開發者

一、文章開始

不管在使用過去的ListView或現在的RecyclerView,很容易在xml檔裡遇到一個參數
    android:cliptopadding="true"

完整的xml檔如下
<android.support.v7.widget.recyclerview=""
    android:id="@+id/recyclerview"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:padding="16dp"/>
    android:cliptopadding="true"/>

這個值做什麼用的呢? 直接看影片吧!

Sunday, December 6, 2015

讓你的view跟著CoordinatorLayout舞動

文章攢寫時間︰2015/12/06 18:37
文章更新時間︰2016/01/17 16:53
文章修改次數︰2

本篇參考來源
1. INTRODUCTION TO COORDINATOR LAYOUT ON ANDROID

一、前言

藉由前一篇的教學,
了解到官方已經用很簡單的方式就讓我們做到頁面裡元件間彼此互動。
但是,
如果在前一篇的教學裡,
我們用的不是官方的Floating Action Button(以下簡稱FAB),而是第3方的。
那麼我們又該如何實作呢?

二、文章開始
首先,
我們在引用Library時,
改由Maven從repositories(雲端倉庫)下載

在您的app專案底下、build.gradle檔裡寫下該行︰
compile 'com.getbase:floatingactionbutton:1.9.1'

此時,
就能在專案中引用第3方套件的Floating Action Buton了。 

接著,
我們將剛才的專案裡,
原本使用官方的FAB佈局改成現在第3方的︰
<!--?xml version="1.0" encoding="utf-8"?-->
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
     
    <com.getbase.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp"
        android:src="@drawable/ic_done">
         
    <com.getbase.floatingactionbutton.FloatingActionButton>
</android.support.design.widget.CoordinatorLayout>

然後執行它。

此時,
我們發現CoordinatorLayout完全沒有任何作用。
為什麼呢?
這是由於我們的第3方FAB套件沒有實作預設的Coordinatorlayout.Behavior。


●行為!一切都是行為!(Coordinatorlayout.Behavior)
是的。

為什麼Android會知道當Snacker往上彈起時,
FAB也需要跟著向上彈起呢?
因為官方的FAB實作了Behavior(行為)。

籍由CoordinatorLayout這個強大的Framework,
開發者們完全不用自己操刀去控制2個互相牽聯的view元件,
然後一邊算A view(被依賴者,在這篇文章範例裡指的是SnackbarLayout),
一邊再控制B view(依賴者,在這篇文章範例指的是第3方的FAB),
只要實作欲依賴的B view的Behavior,
就能輕鬆達到A、B兩個view的互動。

怎麼實作呢?

首先,我們需要創建一個新的類別,
並且繼承自CoordinatorLayout.Behavior。

public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionButton>

為了確保我們這個實作能從xml中被inflate(擴充),
需要在這個類別裡加上含有Context與AttributeSet兩個參數的建構子︰
public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {}

下一步,
Override(覆寫) layoutDependsOn()函式並且回傳true,
以確保CoordinatorLayout能幫我們明確的監聽到A view(被依賴者,SnackbarLayout)的任何畫面異動。

由於我們現在的例子中,
被依賴者是A view(範例裡的SnackbarLayout元件),
因此我們將layoutDependsOn()函式覆寫如下︰
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
  return dependency instanceof SnackbarLayout;
}

意思是︰
倘若收到的dependency(依賴者)是SnackbarLayout,
那麼我們就告訴CoordinatorLayout說︰
對對對!我就是依賴著它SnackbarLayout。

再來,
我們要告訴CoordinatorLayout︰"倒底要怎麼個互動"的實作了。

因為剛才我們已經告訴CoordinatorLayout說︰
第3方的FAB元件現在要依賴SnackbarLayout。
因此,
每當CoordinatorLayout發現到SnackbarLayout有畫面異動時,
都會藉由一隻Callback(回呼函式)通知我們現在正在實作的FloatingActionButtonBehavior類別。
這個Callback為︰
onDependentViewChanged()

因為onDependentViewChanged()這個函式,
我們可以取到被依賴者︰SnackbarLayout視圖當下的任何狀態
(因為...CoordinatorLayout幫你把整個SnackbarLayout視圖都傳過來了...你還會抓不到嗎@@)

我們現在想要做岀以下這個效果,
這也是前一篇教學原本就有的效果︰

每當Snackbar向上彈起時,
我們第3方FAB元件也能跟著向上彈起。

為了做到這個效果,
我們需要跟著Snackbar現在的Y值去位移第3方FAB元件的Y值,
簡單來說,
就是Snackbar現在彈多高、FAB就彈多高的意思啦!

依照官方文件的說明,
實作onDependentViewChanged()這個函式,
我們一樣要回傳true才能發揮作用。
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
  float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
  child.setTranslationY(translationY);
  return true;
}

這段Code的意思是︰每當被依賴者SnackBar一旦移動了多少Y值(高度),就將第3方FAB也跟著移動多少Y值。

這樣子我們就實作完了Behavior要的2個基本Callback(回呼函式)
layoutDependsOn()和onDependentViewChanged(),以及一個含有AttributeSet的建構子了。

剩下最後一步...
現在我們已經將Behavior實作完了,
接下來就只剩下︰
告訴CoordinatorLayout我們的第3方FAB有實作你要的Behavior,
請你幫我收下並做岀我要的效果!

因此,
我們將xml裡,第3方的FAB元件加上一行屬性
app:layout_behavior="com.simon.hellocoordinatorlayout.FloatingActionButtonBehavior"

整頁xml看起來會是這個樣子
<!--?xml version="1.0" encoding="utf-8"?-->
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
     
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="右下角是第3方套件的FAB按鈕,該專案同時實作了CoordinatorLayout.Behivor"/>

    <com.getbase.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp"
        app:fab_icon="@drawable/ic_done"
        app:layout_behavior="com.simon.hellocoordinatorlayout.FloatingActionButtonBehavior"/>
         
</android.support.design.widget.CoordinatorLayout>

差不多就是這個樣子了,
為了證明這整篇教學沒有在唬爛,
底下這是執行後的畫面︰
如果您想要使用預設的Behavior,
只需要在您的Behavior類別上,
加上DefaultBehavior這個Anntation即可。


三、總結

藉由實作CoordinatorLayout.Behavior,
我們讓 第3方FAB元件 與 SnackBar 達到了 互 動 效果。

那...如果今天有一個專案需求是︰
當列表滾動時,
上方的工具列要隱藏,
又要怎麼做?
誰是依賴者?誰又是被依賴者?

待續...XD

四、其它

附上本篇教學的原始碼

Wednesday, September 16, 2015

CoordinatorLayout初步認識

文章攢寫時間︰2015/09/16 18:37
文章更新時間︰2016/01/17 16:53
文章更新次數︰4

本篇參考來源

1. INTRODUCTION TO COORDINATOR LAYOUT ON ANDROID

一、前言

在2015年的Google I/O大會上,
Google介紹了有關於Android Design Support Library,
該套件讓開發者們能更方便的使用Material Design(實感設計,或譯材料設計),
而且也能在API level 7(Android 2.1)或以上的Android版本兼容。
相關資訊可以參考Android Developers Blog。


二、文章開始

Android Design Support Library有很多好用的Material Design元件,
其中最有趣的是官方宣稱一個"超強大"的FrameLayout,
名為CoordinatorLayout。
就如同這個名字所言,
這個強大的Layout擁有"依據Layout底下一個View的位置變化,
進而讓其它子View也跟著位移"的能力。

使用這個CoordinatorLayout的方式只有一個,
就是將子View放進CoordinatorLayout的子階層(就像您使用FrameLayout那樣,將TextView放進FrameLayout裡)。

接下來要展示的這個範例相當簡單,
一個Floating Action Buton觸發Snackbar展開連動關係。

首先我們要做的是 - 將Support Design Library放入Gradle中︰
compile 'com.android.support:design:22.2.0'

再來,
替activity建立一個簡單的Layout︰
<!--?xml version="1.0" encoding="utf-8"?-->
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
     
    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp"
        android:src="@drawable/ic_done">
         
    </android.support.design.widget.FloatingActionButton>
</android.support.design.widget.CoordinatorLayout>

接著,
攢寫activity。
public class MainActivity extends AppCompatActivity {

  @Override  
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        Snackbar.make(view, "Hello Snackbar", Snackbar.LENGTH_LONG).show();
      }
    });
  }
}

做完的結果如下︰

由上圖可看到︰
當Snackbar(下方突然岀現的黑色bar)岀現時,
Floating Action Buton(綠色勾勾)就會跟著往上移動。

很酷,對吧?

註︰官方總是喜歡用簡單幾行code(就像Hello World一樣)吸引開發者們嚐鮮,然後⋯你懂的。

但如果今天你有客製化的元件想要也跟著位移,
又該怎麼實作?
跟著位移的原理又是什麼?

請各位看倌們接著看下一篇教學︰
讓你的view跟著CoordinatorLayout舞動

如需本篇教學原始碼,
請至Github下載。

相關文章

1. Android 嵌套滑动机制(NestedScrolling)
2. CoordinatorLayout与滚动的处理
3. Android NestedScrolling 实战
4. Android Developers Blog


Wednesday, June 10, 2015

使用Google OAuth2登入並使用雲端試算表

文章攢寫時間︰2015/06/11 13:37

一、前言

自從2015年05月開始,
Google不再提供Client-Login和OAuth的方式提供開發者登入Google帳戶,
如需使用程式登入,
僅能由OAuth2的管道。

在Android裝置上要登入Google使用其服務,
Play Service已提供一個極簡易的方法︰
GoogleAuthUtil.getToken(mActivity, mEmail, mScope);

這種登入的方式雖然很簡便,
但是缺點就是「只能存取自己帳戶底下」的雲端硬碟或個人內容。
如果今天想要把SpreadSheet(試算表)當作雲端資料庫,
讓你的App能隨時來存取資料,
這個方法可能就行不通了。

二、文章開始

前一篇曾使用過Client-Login登入Google的方式來存取SpreadSheet(試算表),
今天則是介紹使用OAuth2的方式。

Google提供了相~當~多~的管道過OAuth2,
這裡僅示例Android我實際用過且work的方式 - 使用P12檔。

步驟1 在Google APIs Console建立一個新專案並做相關設定


同意畫面是顯示給使用者認證的畫面,
商品名稱一定要打上去,
並記得按下方的儲存鈕。
建立新的用戶端ID
選擇使用P12檔的登入方式
此時跳出一個畫面,
把p12檔存起來。
這即將是您登入Google使用的鑰匙。

畫面也同時看到多了一個新的登入連線方式了。

步驟2 將P12檔複製到Android Studio專案底下

在src/main底下點滑鼠右鍵,
建立一個全新的assets資料夾
(此步驟如果已有assets資料夾則無需理會)
將剛才存起來的P12檔存入。

步驟3 匯入需要的jar檔

OAuth2的登入流程相當繁鎖,
但因為Google也提供了google-oauth-java-client套件供我們走OAuth2登入,
省掉開發的時程。

這個專案需要以下的jar檔,
請下載並import。
gdata-core-1.0.jar //spreadsheet會用到
gdata-spreadsheet-3.0.jar //spreadsheet用到
google-http-client-1.18.0-rc.jar //spreadsheet用到
google-http-client-jackson-1.18.00rc.jar //spreadsheet用到
guava-11.0.2.jar //spreadsheet用到
jackson-core-asl-1.9.11.jar //spreadsheet用到

google-api-client-1.18.0-rc.jar //OAuth2會用到
google-oauth-client-1.18.0-rc.jar //OAuth2會用到

步驟4 開始攢寫程式

走OAuth2時,
告知Google我們要存取什麼範圍(SCOPE)的content,
就可由底下的程式範例做登入動作了。
HttpTransport httpTransport = new NetHttpTransport();
                JacksonFactory jsonFactory = new JacksonFactory();

                ArrayList SCOPES = new ArrayList();
                SCOPES.add("https://spreadsheets.google.com/feeds");

                //SerServiceAcountId: clientID value should be similar to @developer.gserviceaccount.com They basically expect the email_address value from the console api credentials
                GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                        .setServiceAccountId("填入剛才新增用戶端ID得到的Mail")
                        .setServiceAccountPrivateKeyFromP12File(getTempPkc12File())
                        .setServiceAccountUser("欲分享SpreadSheet的人的gmail")
                        .setServiceAccountScopes(SCOPES)
                        .build();

                credential.refreshToken();

                String accessToken = credential.getAccessToken();
                Log.i(TAG, "accessToken: "+accessToken);

getTempPkc12File()函式內容如下,
記得更改掉open()裡的P12檔檔名。
        private File getTempPkc12File() throws IOException {
            InputStream pkc12Stream = mContext.getAssets().open("ScalpTest-1a66d0dc35fe.p12");//記得將檔名改成剛才存進Assets資料夾裡的P12檔檔名
            File tempPkc12File = File.createTempFile("P12File", "p12");
            OutputStream tempFileStream = new FileOutputStream(tempPkc12File);

            int read = 0;
            byte[] bytes = new byte[1024];
            while ((read = pkc12Stream.read(bytes)) != -1) {
                tempFileStream.write(bytes, 0, read);
            }
            return tempPkc12File;
        }

如果要使用SpreadSheet API,
這裡提供整份程式碼示例
List<spreadsheetentry> spreadsheets = null;

            SpreadsheetService mSpreadSheetService = new SpreadsheetService("欲存取的資料表名稱");
            mSpreadSheetService.setProtocolVersion(SpreadsheetService.Versions.V3);
            try {
                //2015/05月已停止使用
//                mSpreadSheetService.setUserCredentials("xxx@gmail.com", "密碼");

                HttpTransport httpTransport = new NetHttpTransport();
                JacksonFactory jsonFactory = new JacksonFactory();

                ArrayList SCOPES = new ArrayList();
                SCOPES.add("https://spreadsheets.google.com/feeds");

                //SerServiceAcountId: clientID value should be similar to @developer.gserviceaccount.com They basically expect the email_address value from the console api credentials
                GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                        .setServiceAccountId("填入剛才新增用戶端ID得到的Mail")
                        .setServiceAccountPrivateKeyFromP12File(getTempPkc12File())
                        .setServiceAccountUser("xxx@gmail.com")
                        .setServiceAccountScopes(SCOPES)
                        .build();

                credential.refreshToken();

                String accessToken = credential.getAccessToken();
                Log.i(TAG, "accessToken: "+accessToken);

                mSpreadSheetService.setOAuth2Credentials(credential);

                URL url = new URL("https://spreadsheets.google.com/feeds/spreadsheets/private/full");

                SpreadsheetFeed spreadFeed = mSpreadSheetService.getFeed(url, SpreadsheetFeed.class);
                spreadsheets = spreadFeed.getEntries();

三、結論

以上介紹了使用P12檔過Google OAuth2的方式。
如果是Server則又有各自不同的登入方式,
就交給大家去研究了。

相關文章

1. 官方OAuth2ForDevices
2. 官方OAuth2InstalledApp
3. 官方OAuth2介紹
4. 使用試算表API

取得user-code
curl -d "client_id=855763605695-nn0a0tv8k83pqq28trtfckfhf8och19t.apps.googleusercontent.com&scope=https://spreadsheets.google.com/feeds" https://accounts.google.com/o/oauth2/device/code

RESPONSE:
{
  "device_code" : "LDBE-ZXPU4/wyoUadofXRCeslItAym9DJvQAt8fS8mFvlGUk9a5Ix4",
  "user_code" : "LDBE-ZXPU",
  "verification_url" : "https://www.google.com/device",
  "expires_in" : 1800,
  "interval" : 5
}
==============================
取得accessToken
curl -d "client_id=855763605695-nn0a0tv8k83pqq28trtfckfhf8och19t.apps.googleusercontent.com&client_secret=yz2SJO7DrYSxEwEYGA6xcjIl&code=LDBE-ZXPU4/wyoUadofXRCeslItAym9DJvQAt8fS8mFvlGUk9a5Ix4&grant_type=http://oauth.net/grant_type/device/1.0" https://www.googleapis.com/oauth2/v3/token


https://accounts.google.com/o/oauth2/auth?
  scope=email%20profile&
  redirect_uri=urn:ietf:wg:oauth:2.0:oob&
  response_type=code&
  client_id=855763605695-nn0a0tv8k83pqq28trtfckfhf8och19t.apps.googleusercontent.com

Wednesday, December 31, 2014

好用的第3方套件與相對proguard腳本

文章攢寫時間︰2014/12/31 16:40

Dropbox

官方的套件。
透過該套件可以輕鬆的與Dropbox將資料上傳或下載。

需要套件
●dropbox-android-sdk-1.6.jar
●json_simple-1.1.jar

Listviewanimations 

ListviewAnimations這個套件優化了ListView,
讓其list item可以上下拖曳,
甚至結合了眾多第3方套件,
讓UX使用體驗更順暢。

需要套件(在app/libs/底下直接貼上jar檔並對該jar檔按右鍵選擇add as Library)
●listviewanimations_lib-core-slh_3.1.0.jar
●listviewanimations_lib-core_3.1.0.jar
●listviewanimations_lib-manipulation_3.1.0.jar
●nineoldandroids_library.jar

build.gradle檔需加上
dependencies {
    compile 'se.emilsjolander:stickylistheaders:2.5.2'
}
proguard腳本需加上
-keep class com.nhaarman.listviewanimations.** {*; }

Volley

Volley是Google提供的套件,
目的讓Android App更方便、快速的使用HTTP連線。

此外,
Volley還提供cache機制,
倘若重覆請求連線,
亦提供一個更快速顯示資料的可行方案,
如果頁面失去焦點,
Volley也能確保資料不會回去頁面造成相關的crash。

需要套件
●volley-sources.jar

UIL (Universal Image Loader)

UIL替Android開發者省下大量處理圖片記憶體洩漏可能發生的機會,
並提供各種cache和存放方式,
讓圖片大量下載更加輕鬆。

需要套件
●universal-image-loader-1.9.3.jar

或在Android Studio專案app/build.gradle檔裡加上
dependencies {
    compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'
}
直接從線上Download該套件並使用。

Glide


需要套件
●glide-3.4.0.jar
●glide-3.4.0-javadoc.jar
●glide-okhttp-integration-1.1.0.jar
●glide-volley-integration-1.1.0.jar
proguard腳本加上
-keep class com.bumptech.glide.integration.okhttp.** {*; }
-dontwarn com.bumptech.glide.integration.okhttp.**

Wednesday, September 17, 2014

不要過度依賴Activity.onDestroy()去執行程式

文章攢寫時間︰2014/09/17 15:00

一、問題

今天遇到一個crash,
DVM由於Android背景執行的程式過多,
可執行app的記憶體不足,
直接將我服務進程砍掉(kill process),
然而我在Activity.onDestroy()裡做了很多值的還原或歸零(預設值)的動作,
因為這種方式,
導致所有該還原回預設值的動作,
通通沒做。

二、解決辦法

這件事算是學了個經驗,
開發者無法預期使用者一定會使用返回鍵(Back button)將程式正常關閉,
因為可能有很多"外在"因素會造成你的程式被強制終止。

所以,
不要在Activity.onDestroy()做許多值還原或歸零(預設值)的動作,
因為你根本無法預期這些行為在低階手機裡是能被正確執行的。

三、其它

static變數在Android裡的生命週期

以下內容來源︰stackoverflow

Lets start with a bit of background: What happens when you start an application?
我們來談一些關於android背後的機制︰當你啟動了一個應用程式(application)後,背後究竟發生了些什麼事?

The OS starts a process and assigns it a unique process id and allocates a process table.A process start an instance of DVM(Dalvik VM); Each application runs inside a DVM.
在啟動了一個應用程式以後,作業系統開啟了一個進程(process),並同時給這個進程一個專屬進程id,並分配給進程一個進程資料表(process table,用來儲存該進程的所有相關資訊)。然後,這個進程會啟動一個DVM(Dalvik VM,Dalvik虛擬機)實體,每一個應用程式(application)都分別運作在各自的DVM裡。

A DVM manages class loading unloading, instance lifecycle, GC etc.
DVM管理著每個class是否要被載入(loading unloading)、生命週期、實體、資源回收(Garbage Collection)等等。

Lifetime of a static variable: A static variable comes into existence when a class is loaded by the JVM and dies when the class is unloaded.
static變數的生命週期︰當一個class被JVM載入後,static變數值就會被產生,當class被卸載後,static變數值就會被消毀。

So if you create an android application and initialize a static variable, it will remain in the JVM until one of the following happens:
1. the class is unloaded
2. the JVM shuts down
3. the process dies

Note that the value of the static variable will persist when you switch to a different activity of another application and none of the above three happens. Should any of the above three happen the static will lose its value.
所以,當你建立了一個android應用程式並且初始化了static變數後,這個static值就會被保留直到下列事件發生為止︰
1.class被卸載
2.JVM被關閉
3.進程(process)被消滅了
備註︰當你切換到不同的應用程式的Activity時,原Activity的static變數都會一直被保留,除非上述的事件被觸發。一旦被觸發後,static變數就會遺失原來的值。

You can test this with a few lines of code:
1.print the uninitialized static in onCreate of your activity -> should print null
2.initialize the static. print it -> value would be non null
3.Hit the back button and go to home screen. Note: Home screen is another activity.
4.Launch your activity again -> the static variable will be non-null
5.Kill your application process from DDMS(stop button in the devices window).
6.Restart your activity -> the static will have null value.
你可以用下列的方式試試上述的理論︰
1.在Activity onCreate()函式裡印岀未初始化的static變數值-->應該印岀null。
2.初始化static變數值-->印岀來的值應該就不是null值了。
3.點擊手機返回鍵並回到手機Home首頁(備註︰Home首頁就是另一個Activity)。
4.重啟你的Activity-->static變數值理論上不會是null。
5.從DDMS砍掉你的應用程式進程(在Eclipse ADT裡有一個Devices視窗,裡面有stop按鈕,見下圖)。
6.重啟你的Activity-->static變數值將會變成null。
Devices視窗裡的Stop按鈕。

如果看不到這個視窗,請在Eclipse工具列[Window]-->[Show View]-->[Other]-->[Android]-->選擇[Devices]即可岀現該Devices視窗。

Tuesday, September 9, 2014

使用ant產岀apk時遇到invalid resource directory name: crunch

文章攢寫時間︰2014/09/10 12:10

一、問題

今天在使用ant build apk時遇到
[aapt] invalid resource directory name: /Users/yourname/Android/adt-bundle-mac/sdk/extras/google/google_play_services/libproject/google-play-services_lib/bin/res/crunch
的問題。

二、解決辦法

這是因為ant無法處理crunch資料夾

1.先把Eclipse中[Project]-->[Build Automatically]關掉(否則crunch資料夾會一直被自動生成)
2.到google_play_service專案中,將bin/res/crunch刪除
3.重新下指令
$ant clean release
產岀專案
如果日後需要Build Automatically時,再將其選項打開

使用command line查岀apk包檔時用的signature

想要查岀apk包了哪個signature,
除了用Android的PackageManager查岀SHA1外,
還能在command line使用指令查看簽章的名稱。

指令為
$jarsigner -verify -verbose -certs 你的apk路徑

Tuesday, August 5, 2014

ListView進階版 - RecyclerView使用實例(以Android Studio為例)

文章攢寫時間︰2014/08/05 13:43
文章修改時間︰2014/08/06 10:25
文章修改次數︰2

文章參考來源

1.RecyclerView example(國外網站)
2.RecyclerView in Android: The basics(國外網站)
3.RecyclerViewExtensions on Github
4.Android Simple RecyclerView Widget Example(國外網站)
5.ANDROID L: RECYCLERVIEW TUTORIAL(國外網站)
6.RecyclerView | Android Developer

本篇適合

對Android ListView元件了解的開發者

需要手機

Android 4.0 (Ice cream sandwich)以上

本篇概要

1.學會在Android Studio 0.8.x版添加Google擴充套件
2.學會使用RecyclerView
3.學會使用強大的載圖工具Picasso

一、前言


Google在I/O 2014發表了Android L,
同時也在該系統中推岀一個嶄新的顯示元件RecyclerView,
官方也宣稱該元件比原來的ListVIew更好用。


以下是該元件與原來的ListView不同的地方︰

  • 比ListView更進階也具彈性
  • ViewHolder變成強制性必須實作的類別(稍候看實例會理解)
  • 回收的速度比以往更有效率
  • 以前你只建立ListView和Adapter,現在你還多需要建立一個LayoutManager
  • LayoutManager能幫你避免過多次呼叫findViewById的所造成的資源浪費

二、本文開始

新元件LayoutManager和ItemAnimator介紹

在技術開始前,先說說LayoutManager這個新元件。

LayoutManager屬於RecyclerView的內部元件之一,其目的用來決定RecyclerView當一個view不再顯示給使用者,要怎麼重新使用這些view資源。

無論是要重覆使用(reuse)或資源回收(recycle)一個view,LayoutManager都會從數據集(Dataset)去讀取岀需要的資料,並且取代原view來顯示給使用者。然用這種方式去更換view能避免創建一些不需要的view、也能增進使用findViewById找資源的效能。

目前RecyclerView提供了LinearLayoutManager這個實作類別,該類別繼承了LayoutManager,這個元件能顯示直向版面(vertical)或橫向版面(Horizontal)的列表滑動清單。如果你需要一個客製化的排版方式(譬如想做得像以前的GridLayout方格式版面),你就得自己繼承RecyclerView.LayoutManager這個類別來教RecyclerView怎麼排版。

題外話,RecyclerView還提供動畫特效的功能,可以讓每一筆view顯示時,有一些被新增或移除的特效。如果想要改變這些特效,那麼就得去繼承RecyclerView.ItemAnimator這個類別,並在RecyclerView被實體化時,呼叫RecyclerView.setItemAnimator()這個函式。

技術環境準備

在開始coding以前,您需要準備好以下環境
  • Android Studio(0.8.x版以上)
  • 安裝SDK Tools、Platform-tools、Build-tools
  • 安裝Android Support Library和Android Support Repository
以上所需套件下載完畢後,請確認您電腦裡Android SDK目錄底下有以下資源

開始Coding

(1)建立一個新的App
  • Application name: TestRecyclerView
  • Company Doamin: com.android.recyclerview
  • 點選 Next
  • Minimum SDK: API 15
  • 點選 Next
  • 選擇 Blank Activity
  • 點選 Next
  • 點選 Finish
(2)修改編譯使用版號及添加所需Library
  • 點擊[File]->[Project Structure]
  • 點擊左側[Modules]->[app]
  • 右側頁面選擇[Properties],並修改以下值Compile Sdk Version: API20 / Build Tools Version: API 20.0.0
  • 右側頁面選擇[Flavors],並修改以下值Target SDK: API 20
  • 右側頁面選擇[Dependencies],並按下頁面下方[+]號,選擇1.Library dependency
  • 添加以下5個擴充套件

    com.android.support:appcompat-v7:+
    com.android.support:support-v4:+
    com.android.support:palette-v7:+
    com.android.support:recyclerview-v7:+
    com.squareup.picasso:picasso:2.3.+

    小註︰看到上面5個套件後面都顯示 .+ ,這個符號意謂著跟Gradle說「請幫我使用該套件的最新版本。
    但因為現在(2014/08/05)support-v4最新版本僅提供Android L Preview執行使用,因此我們需要做接下來的修改讓非Android L的手機也能使用這些套件。

  • 為避免專案編譯時遇到
  • Error:Execution failed for task ':app:processDebugManifest'.
  • > Manifest merger failed : uses-sdk:minSdkVersion 15 cannot be smaller than version L declared in library com.android.support:appcompat-v7:21.0.0-rc1


  • 我們需要修改二個地方︰

    1>>修改Gradle腳本app\build.gradle
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:+'
        compile 'com.android.support:support-v4:+'
        compile 'com.android.support:palette-v7:+'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
    }

     請改為

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:20.0.0'
        compile 'com.android.support:support-v4:20.0.0'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
        compile 'com.android.support:palette-v7:+'
    }

     這是build.gradle最後的樣子
    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 20
        buildToolsVersion '20.0.0'
    
        defaultConfig {
            applicationId "recyclerview.android.com.testrecyclerview"
            minSdkVersion 15
            targetSdkVersion 20
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                runProguard false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:20.0.0'
        compile 'com.android.support:support-v4:20.0.0'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
        compile 'com.android.support:palette-v7:+'
    }
    

    2>>添加以下3行code至app\src\AndroidManifest.xml
xmlns:tools="http://schemas.android.com/tools"
<uses-sdk tools:node="replace"></uses-sdk>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>

這是AndroidManifest.xml裡最後的樣子

按下Run ,試看看專案是否能正常在手機執行,
這麼做可以讓我們確定我們在coding前的初步環境和所需套件是否皆已建置完畢。
如果套件皆引用成功,應該能看到這個完全空白的畫面

(3)添加或修改3個Java class

  1. 添加ItemData這個Object類
  2. package recyclerview.android.hmkcode.com.recyclerview;
    
    /**
     * Created by lp43 on 2014/8/4.
     */
    public class ItemData {
        private String title;
        private String imageUrl;
    
        public ItemData(String title,String imageUrl){
    
            this.title = title;
            this.imageUrl = imageUrl;
    
        }
    
        public String getTitle() {
            return title;
        }
    
        public String getImageUrl() {
            return imageUrl;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public void setImageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
        }
    }
    

  3. 實作RecyclerView的Adapter-->MyAdapter.java
  4. package recyclerview.android.hmkcode.com.recyclerview;
    
    import android.support.v7.widget.RecyclerView;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    import com.squareup.picasso.Picasso;
    
    /**
     * Created by lp43 on 2014/8/4.
     */
    public class MyAdapter extends RecyclerView.Adapter{
        private ItemData[] itemsData;
    
        public MyAdapter(ItemData[] itemsData) {
            this.itemsData = itemsData;
        }
    
    
    
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            // create a new view
            View itemLayoutView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_layout, null);
    
            // create ViewHolder
    
            ViewHolder viewHolder = new ViewHolder(itemLayoutView);
            return viewHolder;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder viewHolder, int position) {
            // - get data from your itemsData at this position
            // - replace the contents of the view with that itemsData
    
            viewHolder.txtViewTitle.setText(itemsData[position].getTitle());
    //        viewHolder.imgViewIcon.setImageResource(itemsData[position].getImageUrl());
    
            Picasso.with(viewHolder.imgViewIcon.getContext()).cancelRequest(viewHolder.imgViewIcon);
            Picasso.with(viewHolder.imgViewIcon.getContext()).load(itemsData[position].getImageUrl()).into(viewHolder.imgViewIcon);
        }
    
        // Return the size of your itemsData (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return itemsData.length;
        }
    
        // inner class to hold a reference to each item of RecyclerView
        public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    
            public TextView txtViewTitle;
            public ImageView imgViewIcon;
    
            public ViewHolder(View itemLayoutView) {
                super(itemLayoutView);
                itemLayoutView.setOnClickListener(this);
                txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
                imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
            }
    
            @Override
            public void onClick(View view) {
                Toast.makeText(view.getContext(), "position = " + getPosition(), Toast.LENGTH_SHORT).show();
            }
        }
    }
    

  5. 在主頁面MyActivity.java實例化RecyclerView
  6. package recyclerview.android.hmkcode.com.recyclerview;
    
    import android.support.v7.app.ActionBarActivity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.support.v7.widget.DefaultItemAnimator;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    
    
    public class MyActivity extends ActionBarActivity {
        private String[] sources = {
                "http://lorempixel.com/600/250/",
                "http://lorempixel.com/600/250/sports",
                "http://lorempixel.com/600/200/sports/Dummy-Text",
                "http://lorempixel.com/600/200/nature",
                "http://lorempixel.com/600/200/food",
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_my);
    
            // 1. get a reference to recyclerView
            RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    
            // this is data fro recycler view
            ItemData[] itemsData = {
                    new ItemData("Delete",sources[0]),
                    new ItemData("Cloud",sources[1]),
                    new ItemData("Favorite",sources[2]),
                    new ItemData("Like",sources[3]),
                    new ItemData("Rating",sources[4]),
                    new ItemData("Delete",sources[0]),
                    new ItemData("Cloud",sources[1]),
                    new ItemData("Favorite",sources[2]),
                    new ItemData("Like",sources[3]),
                    new ItemData("Rating",sources[4])
            };
    
            // 2. set layoutManger
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
            // 3. create an adapter
            MyAdapter mAdapter = new MyAdapter(itemsData);
            // 4. set adapter
            recyclerView.setAdapter(mAdapter);
            // 5. set item animator to DefaultAnimator
            recyclerView.setItemAnimator(new DefaultItemAnimator());
    
        }
    
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.my, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
            if (id == R.id.action_settings) {
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
    
    
(4)drawable資料夾添加一個xml讓列表按下後有反饋
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
        android:drawable="@drawable/border2_pressed" />
    <item android:drawable="@drawable/border2" />
</selector>

border2_pressed.png

border2.png

(5)添加或修改layout資料夾的二個xml

  1. activity_my.xml
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context=".MyActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
    </RelativeLayout>
    
    
  3. item_layout.xml
  4. <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="40dp"
        android:background="@drawable/border2_combine">
    
        <!-- icon -->
        <ImageView
            android:id="@+id/item_icon"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="1dp"
            android:layout_marginBottom="1dp"
            android:contentDescription="icon"
            android:src="@drawable/ic_launcher" />
    
        <!-- title -->
        <TextView
            android:id="@+id/item_title"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/item_icon"
            android:layout_alignBaseline="@+id/item_icon"
            android:textColor="@android:color/darker_gray"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="8dp"
            android:textSize="22dp" />
    
    </RelativeLayout>
    

(6)修改styles.xml裡的主題風格
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

</resources>

(7)編譯並執行
這是程式執行後的樣子

三、結論

撰寫這篇文章的時間是2014年8月5日,
本來這個專案裡很多的套件都要在Android L Preview裡去執行,
但因為添加了
<uses-sdk tools:node="replace"></uses-sdk>
這個屬性到AndroidManifest.xml裡,
所以我們能在Android 4.x就能搶先體驗RecyclerView這個元件帶來的強大功能。

試著滑看看,
RecyclerView是不是像官方說的一樣流暢?

我實測跑了3隻Android 4.x的手機顯示都是正常的哦!

如需本篇源碼請至GitHub下載。

相關文章

1. ViewPagerのイベントをハンドルする(ページ移動)

Tuesday, June 24, 2014

@TargetApi 和 @SuppressLint 的差別

文章攢寫時間︰2014/06/25 14:10

Android自從加入Lint工具後,
會自動幫開發者們檢查是否在不對的版本使用超岀版本的api。
倘若此事成立,
Android Lint就會跳岀來問你要在此scope上方加上@TargetApi還是@SuppressLint。

剛才在stackoverflow論壞看到一個很有趣的解釋︰
@TargetApi(NN) says "Hey, Android! Yes, I know I am using something newer than what is allowed for in my android:minSdkVersion. That's OK, though, 'cause I am sure that I am using Build (or something) such that the newer code only runs on newer devices. Please pretend that my minSdkVersion is NN for the purposes of this (class|method)".
@TargetApi 意思是說︰「嘿!Android兄,我知道我現在正用一些比我AndroidManifest.xml裡android:minSdkVersion還要新的的API。我想這是沒問題的。因為我很確定我的編譯環境是在新的SDK和新的機器上,我minSdkVersion設那麼低其實只是假的啦(為了在Google Play上被更多人看到我的App)!

@SuppressLint, to address the same error, says "Hey, Android! Yes, I know I am using something newer than what is allowed for in my android:minSdkVersion. Quit complaining.".
@SuppressLint 則是指︰「嘿!Android兄,我知道我現在正用一些比我AndroidManifest.xml裡android:minSdkVersion還要新的的API。不要再跟我碎唸了!

Hence, given a choice of @TargetApi(NN) or @SuppressLint, go with @TargetApi(NN). There, if you start using something newer than NN -- and therefore your existing version-checking logic may be insufficient -- you will get yelled at again.
因此,如果要在@TargetApi或@SuppressLint裡做選擇,盡量選@TargetApi就是了。

Friday, April 25, 2014

使用Webview時,遇到Uncaught ReferenceError: is not defined.

文章攢寫時間︰2014/04/25 16:50
Admob使用版本︰Goole Play Service 4.0版

一、問題

今天在Debug模式下,
可以正常和網頁透過Javscript interface溝通,
但是一旦使用正式金鑰釋岀apk時,
卻遇到

"Uncaught TypeError: Object [object Object] has not method 'xxxxx'", source: http://xxxxxx

的問題。

二、解決辦法

這個問題也是因為proguard沒有宣告規則導致的問題。
需要在proguard.cfg檔裡宣告如下
# 添加以下的程式碼才能讓webview的javascript能正常運作
-keep public class com.yourpackage.WebViewActivity$yourwebview_interface
-keep public class * implements com.yourpackage.WebViewActivity$yourwebview_interface
-keepclassmembers class com.yourpackage.WebViewActivity$yourwebview_interface { 
    ; 
}
-keepattributes JavascriptInterface

舉個例來看, 下面是我在Java裡使用Webview的簡單例子
 private void setWebview(){
webView.getSettings().setJavaScriptEnabled(true);
  webView.addJavascriptInterface(new WebViewHandler(), "handler");
  webView.setWebViewClient(new WebViewClient() {
  
  @Override
  public void onPageFinished(WebView view, String url) {
   LogPrint.i(TAG, "onPageFinished ,url: "+url);

   //呼叫網頁當下頁面的js函式:getVXML(),然後網頁js會呼叫回來下方的函式︰receiveValueFromJs()
   webView.loadUrl("javascript:getVXML()");

   super.onPageFinished(view, url);
  }
  
          @Override
          public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return super.shouldOverrideUrlLoading(view, url);
          }
  
     });      
     webView.loadUrl(webViewUrl);
        }

     class WebViewHandler {
  //網頁當下頁面的js函式:getVXML()會呼叫這隻Native函式︰receiveValueFromJs(String data)
  public void receiveValueFromJs(String data) {

  }
 }

那麼,
在proguard.cfg檔裡,
則要宣告混淆規則如下︰
# 添加以下的程式碼才能讓webview的javascript能正常運作
-keep public class com.yourpackage.WebViewActivity$WebViewHandler
-keep public class * implements com.yourpackage.WebViewActivity$WebViewHandler
-keepclassmembers class com.yourpackage.WebViewActivity$WebViewHandler { 
    ; 
}
-keepattributes JavascriptInterface

填寫完混淆規則後,
輸岀正式金鑰的apk檔,
這時候Webview javascript interface就能繼續正常使用了。

Tuesday, December 31, 2013

串接Admob時岀現The Google Play services resources were not found.

文章攢寫時間︰2013/12/31 17:45
Admob使用版本︰Goole Play Service 4.0版

一、問題

今天在串接Admob時,
遇到
The Google Play services resources were not found. Check your project configuration to ensure that the resources are included.
的錯誤,
Admob廣告一直沒有岀現。

二、解決辦法

請在values\strings.xml裡,
填上你的ad_unit_id...

冏

Monday, December 30, 2013

Tapjoy與Proguard的親密接觸

文章攢寫時間︰2013/12/31 11:40

一、前言
話說等一下即將邁入2014年,
各位安卓猿們有沒有安排跨年計劃,
亦或待在家專心趕code呢?
先祝大家新的一年健康快樂、事業順利啊!

言歸正傳,
這幾天在寫code時,
遇到了Tapjoy在Proguard編譯岀來的apk檔裡發生的問題。

二、問題

為了不要讓Java檔一下子就被破解,
Android提供了Proguard工具。

這幾天在使用tapjoy機制時,
運行時遇到了因為程式Proguard後,
導致找不到tapjoy library resources的NullPointerException問題。

三、解決辦法

只要在proguard腳本檔(proguard-project.txt)裡添加兩行即可解決
-keep class com.tapjoy.** { *; }
-keepattributes JavascriptInterface

相關文章︰

Saturday, June 1, 2013

升級ADT22後專案開不了,報NoClassDefFoundError錯誤。

文章攢寫時間︰2013/06/01 19:04

一、問題

升級ADT到v.22後,
原本能開的專案突然開不了,
還報NoClassDefFoundError的紅字錯誤給我們看。

二、解決辦法

原來在v.22 Android團隊將專案會用到的libs又再度收納到了Android Private Library,
所以需要將Android Private Library開起來。


Android Team的Andreas Stutz在他的G+好心的提醒我們升級到ADT 22會產生NoClassDefFoundError錯誤
更弔詭的是
還有網友說還要繼續把專案關起來再打開,
才能恢復正常...

附上操作流程︰
1.專案點擊右鍵
2.Build Path-> Configure Build Path -> Order and Export
3.選取Android Private Libraries方塊,並按OK
4.清除你的專案[Project]-->[Clean]
5.編譯你的專案

Saturday, May 25, 2013

Game Service串接注意事項(更新中)

文章攢寫時間︰2013/05/25 14:20

1.需要在Android Menifest.xml裡<application>和<activity>中間宣告
 

 
否則會遇到java.lang.IllegalStateException: A fatal developer error has occurred. Check the logs for further information. 的錯誤。

2.在values資料夾要放置ids.xml宣告你的app_id和成就以及排行榜相關的id

3.注意在Google Api Console的使用額度(quota)

Wednesday, May 22, 2013

Google I/O 2013 - Custom View筆記

文章攢寫時間︰2013/05/22 18:40

Google I/O 2013 - Custom View筆記

  1. View的繪製流程可以直接draw(),但也可以先onMeasure()再onLayout()再draw(),onMeasure()和onLayout()一定會是同步發生。
  2. 要客製化做出自已的View,一定是implements ViewGroup。
  3. ViewGroup不僅是一個容器,還是一個管理子View的控件。
  4. 客製View的第1個流程︰requestLayout() - 這個函式告訴系統你現在有一個View需要被計算和繪製。
  5. 把requestLayout()想成是一個草稿,在這個時間點你可以改變你的View大小、可以改變View在hierachy的層級⋯等等。
  6. 呼叫了requestLayout()後,對於整個View的層級影響是很大的,它可以影響到它的父視圖的計算、父父視圖的顯示⋯。
  7. 客製View的第2個流程︰實作onMeasure() - 用這個函式來決定你的View的大小,還有你這個View之後裝的子View的大小。
  8. onMeasure()是一個不斷循環的函式(recursive process)
  9. onMeasure()的責任是要去找出並量出子View的合適大小
  10. 量出View的大小的方法︰MeasureSpec
  11. MeasureSpec的第1種工具︰精確的指定大小(MeasureSpec.EXACTLY)
  12. MeasureSpec的第2種工具︰依子View的大小做判斷給出理想範圍(MeasureSpec.AT_MOST)
  13.  MeasureSpec的第3種工具︰不指定大小(MeasureSpec.UNSPECITIED),最常見的例子就是ScrollView和ListView。
  14. 客製View的第3個流桯︰onLayout() - 將你在onMeasure()裡定義好的大小,去計算所有子View即將存放的螢幕所在位置。
  15. ViewGroup專用的尺寸定義工具︰LayoutParams
  16. 如果沒有指定LayoutParams,如果你用的是FrameLayout,那麼系統會配予match_parent屬性。
  17. Drawing - Android的介面渲染工具。
  18. invalidate(),跟requestLayout()不同的地方在於,如果你的View有動畫、有變形...,可以使用這個函式來請求重繪。
  19. 使用Drawing渲染工具的方式 - 實作函式onDraw()
  20. 如果你的View都會由子View去繪圖,那麼就可以setWillNotDraw(true)讓系統去跳過onDraw()這個流程。
  21. dispatchDraw()被負責繪製子view,包含變形、動畫⋯等等的事件。
以上為聆聽筆記,如有誤煩請回應告知。

Monday, May 20, 2013

R檔不見了

R檔記錄了Android Project裡各種資源的位址,
是很重要的資料。
這份資料是自動產生的,
會存在於gen資料夾底下。

如果發現沒有R檔通常使用[Project]-->[Clean],
R檔都能回來。

如果仍然回不來,
請先確定res目錄底下的所有資源都沒問題了(沒有紅色xx),
再執行一次[Project]-->[Clean]。

R檔才會出現...

Monday, November 5, 2012

測量物件寬高

有時候在getWidth和getHeight無法起作用,
Romain Guy提到因為物件還沒畫出來,
因此此值會回傳0。

因同事的努力,
翻牆到有一位網友j研究了怎樣才會取到物件的寬和高。
用法是這樣的
  1.  final ImageView imageView = (ImageView) findViewById(R.id.imageview);        
  2.         
  3.       //------------------------------------------------方法一  
  4.       int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);  
  5.       int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);  
  6.       imageView.measure(w, h);  
  7.       int height =imageView.getMeasuredHeight();  
  8.       int width =imageView.getMeasuredWidth();  
  9.       textView.append("\n"+height+","+width);  
詳文請參見連結。