2014年1月30日 星期四

Android 開發 (二十一) 使用 URL開啟app

假設我們寫了一個很龐大的app裡面有很多功能,
然後今天來了一個使用者,他希望找到我們app裡的某個功能,
我們當然不可能直接前去指導那個使用者如何使用
這時我們可以使用 intent-filter 使我們的app導向特定頁面

又或者我在FB粉絲團留言中只提供簡略的描述,
當使用者點擊網址我希望能利用網址導向app的詳細頁面。

要實做這個功能就必須使用 intent-filter



 <activity
            android:name="com.example.intentfiltersample.MainActivity"
            android:label="@string/app_name" >
            <intent-filter android:priority="0">
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                   android:path="com.*"
                    android:scheme="http" >
                </data>
            </intent-filter>
        </activity>

這樣只需要在facebook上 貼文   http://com.functionA   http://com.functionB  並點擊
這樣就可以導向不同的功能頁面

接著只需要在接到時 getIntent().getDataString();  就可以取得網址了
附上 sample Code

2014年1月29日 星期三

Android 開發 (二十) GridView 與scrollView共同使用

在開發專案時有些時候會需要類似下面左圖的樣式
上方是一個view ,下方是gridview,但是在滑動的時候卻是整片UI滑動,
也就是當滑動時會變成像右圖的樣式。


要實做這種view 有兩種方式
一種是在ScrollView裡面動態的將view 加入
另一種方法是使用 ScrollView裡面包含GridView

關於第一種作法,之後有空會在詳細說明
現在要介紹的是第二種方法,當我在實作這個方法時,
我天真的以為只需要在scrollview 裡面包 gridview就好了
程式碼如下


<ScrollView 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:background="#333333"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:background="#a5c9ff"
            android:gravity="center_horizontal|center_vertical"
            android:text="@string/hello_world" />

        <GridView
            android:id="@+id/gridview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:horizontalSpacing="3dp"
            android:numColumns="2"
            android:stretchMode="columnWidth"
            android:verticalSpacing="3dp" />
    </LinearLayout>

</ScrollView>

當我開開心心的將code 寫好,deploy到手機之後,
這才發現現實是殘酷的,事情永遠不是像我想得那麼簡單,
gridview只顯示出兩個item,其他的item不翼而飛。

為了要解決這個問題,就必須客製化gridview


public class MyGridView extends GridView { 
    public MyGridView(Context context) { 
        super(context); 
    } 
    public MyGridView(Context context, AttributeSet attrs) { 
        super(context, attrs); 
    } 
    public MyGridView(Context context, AttributeSet attrs, int defStyle) { 
        super(context, attrs, defStyle); 
    } 
    @Override 
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
 
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, 
                MeasureSpec.AT_MOST); 
        super.onMeasure(widthMeasureSpec, expandSpec); 
    } 
} 

在onMeasure的時候調整高度,然後更改原本的gridview改成客製化的gridview
這樣所有的問題就都解決了。

附上 sample Code

2014年1月28日 星期二

Android 開發 (十九) BroadcastReceiver

使用 BroadcastReceiver  讓我們可以去接收某些Action,例如手機沒電,或者某些自己特定的action,在 OnReceive的時候我們可以做一些接收到這個action時所需作的處理,
在這邊寫一些範例


   @Override
   public void onClick(View arg0) {
           Intent intent = new Intent().setAction(  
                         "myAction").putExtra("Tag",  
                         "Data");  
                  
              sendBroadcast(intent);  
   }
   

在button click的時候 ,sendBroadcast 送廣播給手機, 並且設定 action為我自己客製化的action,
這時所有監聽這個action的都會被呼叫。


    public void onReceive(Context context, Intent intent) {  
     Log.d("Ted","OnReceive");
        this.context = context;  
        if(intent.getAction().equals("myAction")){  
            Log.v("Ted", intent.getStringExtra("Tag"));  
            showNotification();  
        }  
        
    } 

我在接收到action的時候發送Notification,使手機出現Notification。

附註:
使用BroadcastReceiver 必須在manifest裡增加receiver,範例如下


        <receiver android:name="Receiver1" >
            <intent-filter>
                <action android:name="myAction" >
                </action>
            </intent-filter>
        </receiver>

myAction為客製化的action,在button click時我有設定action,所以在button click之後會去呼叫Receiver1,並且在 OnReceive可以做相關的動作。

附上 sample Code

Android 開發 (十八) Notification

甚麼是Notification



可以看到上圖的NavigateTo SecondActivity這個 Notification
有很多地方可以看到Notification的應用
例如: 收到信件,需要軟體更新時,收到GCM時,鬧鐘

在這邊會稍微介紹如何使用Notification



    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Intent notifyIntent = new Intent(MainActivity.this,
      SecondActivity.class);
    notifyIntent.putExtra("SecondActivity", "Btn");
    
    PendingIntent appIntent = PendingIntent.getActivity(
      MainActivity.this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    Notification notification = new Notification();
    notification.icon = R.drawable.ic_launcher;
    notification.tickerText = "notification on status bar.";
          notification.flags |= Notification.FLAG_AUTO_CANCEL; 
    notification.setLatestEventInfo(MainActivity.this,
      "NavigateTo", "SecondActivity", appIntent);
    notificationManager.notify(0, notification);

首先先取得 NotificationManager  接著創造 intent ,  SecondActivity為點擊時倒向的Activity,
PendingIntent用來存取 intent ,其中有個重點 PendingIntent.FLAG_UPDATE_CURRENT為必要的,假設缺少這行會造成SecondActivity在getIntent的時候只會取得第一次的Intent,
如果需要瞭解這行的用意,只需要實作多個Intent帶入不同參數,並倒向同一個activity就可以複製出這個問題。

接著創造 Notification  並且做相關設定,其中Notification.FLAG_AUTO_CANCEL為點擊時Notification會自動消失。
在notificationManager.notify之後就會產生出notification。

接著要介紹如何客製化UI


    RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.customview);

    contentView.setImageViewResource(R.id.image, R.drawable.ic_launcher);

    contentView.setTextViewText(R.id.text, "Hello, this message is in a custom expanded view");
    Intent notifyIntent = new Intent(MainActivity.this,
      MainActivity.class);
    
    PendingIntent appIntent = PendingIntent.getActivity(
      MainActivity.this, 0, notifyIntent, 0);
    int icon = R.drawable.ic_launcher;
       long when = System.currentTimeMillis();
       Notification notification = new Notification(icon, "Custom Notification", when);
         // Notification notification = new Notification();
    notification.contentIntent = appIntent;
    notification.contentView = contentView;
          notification.flags |= Notification.FLAG_AUTO_CANCEL; //Do not clear the notification
          notification.defaults |= Notification.DEFAULT_LIGHTS; // LED
          notification.defaults |= Notification.DEFAULT_VIBRATE; //Vibration
          notification.defaults |= Notification.DEFAULT_SOUND; // Sound
         
          
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    notificationManager.notify(1, notification);

創建 RemoteViews contentView之後,並且設定 notification.contentView = contentView;
其中 R.layout.customview 為任意客製化UI。

在這裡附上 sample code

2014年1月27日 星期一

Android 開發 (十七) Hello GCM

甚麼是GCM


server端可以藉由此通知client端一些消息,例如: line的訊息通知,軟體更新通知,或者某些商品的特賣通知。

如何實現GCM


首先必須先寫出client端程式,client端必須註冊GCM,並且設定一個receiver去接收GCM

以下是範例


  GCMRegistrar.checkDevice(this); 
  GCMRegistrar.checkManifest(this); 
  final String regId = GCMRegistrar.getRegistrationId(this); 
  Log.d("Ted", "regId = " + regId); 
  if (regId.equals("")) { 
     GCMRegistrar.register(this, Project_ID); 
  } else { 
     Log.d("Ted", "Already registered"); 
  }              

上面幾行會註冊GCM,接著在manifest裡面加入以下的程式


   <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <permission
        android:name="com.example.momoandroid.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="com.example.momoandroid.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.gcmsample.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name="com.google.android.gcm.GCMBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

                <category android:name="com.example.gcmsample" />
            </intent-filter>
        </receiver>

        <service android:name=".GCMIntentService" />
    </application>

使用receiver接收gcm,並且設定service為 GCMIntentService
然後GCMIntentService繼承 GCMBaseIntentService
接著在onMessage也就是收到訊息時做相關的動作


  NotificationManager notificationManager = (NotificationManager) context
    .getSystemService(NOTIFICATION_SERVICE);

  /*
   * Intent notifyIntent = new Intent(context ,MainActivity.class);
   * notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent
   * appIntent = PendingIntent.getActivity(context,0, notifyIntent, 0);
   */

  Notification notification = new Notification();
  notification.icon = R.drawable.ic_launcher;
  notification.tickerText = "notification on status bar.";
  notification.defaults = Notification.DEFAULT_ALL;
  notification.setLatestEventInfo(context, "Title", message, null);
  notificationManager.notify(0, notification);

如上面的程式碼,這樣就可以在收到訊息時產生推撥
然後在 OnRegiter時將 Register_Id記起來之後在server端會使用到

上面的程式碼有個Project_ID必須去google developer console申請
首先去 google console 開啟一個project  https://cloud.google.com/console/project
接著就可以在overview欄位找到Project Number ,也就是我們需要的Project_ID

接著開啟 google cloud messaging for android


接著create key create browser key


接著將API key 記起來  之後在server端需要使用

目前我們已經取得了 server 端需要的API_Key 以及 Register_Id
接著我們只需要開創一個java project
然後key入以下程式碼

 Sender sender = new Sender(API_Key );

            ArrayList<String> devicesList = new ArrayList<String>();
            
            String device =Register_Id;
           devicesList.add(device);

            Message message = new Message.Builder()
                    //.collapseKey("message")
                    //.timeToLive(241000)
                    .delayWhileIdle(true)
                    .addData("message", "Your message send")
                    .build();



          
            MulticastResult result = sender.send(message, devicesList, 1);
      

            System.out.println(result.toString());
            if (result.getResults() != null) {
                int canonicalRegId = result.getCanonicalIds();
                if (canonicalRegId != 0) {
                }
            } else {
                int error = result.getFailure();
                System.out.println(error);
            }

當執行完java project 訊息就會被送出,然後client端就會出現如下圖的推撥


在這邊附上 sample code
附註   java project 必須 import一些jar檔  檔案也會附在sample code裡
GCMSample為 client端
sendMessage為 server端

2014年1月22日 星期三

Robolectric介紹(一) shadow

Why Use Robolectric

因為使用android 模擬器跑test的速度實在是太慢了, Robolectric讓我們可以再IDE上面run TDD
run test的速度也快得多。

安裝的介紹在  Android 開發 (十六) 使用Mockito和Robolectric寫 test case 裡面有詳細的介紹

今天要介紹的是shadowOf的應用


What is shadowOf  

有時候 android並未提供某些method Robolectric的 shadowOf就提供的那些method供我們測試用
舉例來說

在MainActivity的  UI為


    <ImageView
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />

我們希望測試ImageView 的 src在程式中是否為 R.drawable.ic_launcher
我們寫出下面的測試

 public void TestDrawable()
 { 
  MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().visible().get();
  ImageView img = (ImageView) activity.findViewById(R.id.img);
  ShadowImageView shadowImageView = Robolectric.shadowOf(img);
  assertEquals(Robolectric.shadowOf(
    shadowImageView.getDrawable()).getCreatedFromResId(),
    R.drawable.ic_launcher);
 }


上面是測試案例,先取得imageview接著
使用shadowOf 取得 shadowImageView
然後比較android:src 是否為  R.drawable.ic_launcher
使用test之後可以看到是綠燈。


shadowOf還有其他的功能
例如以下的程式


 @Override
 public void onClick(View arg0) {
  // TODO Auto-generated method stub
  startActivity(RecentActivityActivity.class);
 }

在click的時候切換到名為 RecentActivityActivity 的Activity
我們希望在測試的時候測試當click之後是否會切換到正確的activity


 @Test
 public void TestActivity()
 {
  MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().visible().get();
  Button btn= (Button)activity.findViewById(R.id.btn);
  btn.performClick();
  ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
  Intent startedIntent = shadowActivity.getNextStartedActivity();
     ShadowIntent shadowIntent =Robolectric.shadowOf(startedIntent);
  assertThat(shadowIntent.getComponent().getClassName(), equalTo(RecentActivityActivity.class.getName()));
 }

上面的程式碼使用 btn.performClick() 觸發 onclick event
接著取得 shadowIntent 並且  判斷新的 intent名稱是否為 RecentActivityActivity


另外一個常用的測試項目,判斷字串是否正確


 @Test
 public void TestHelloWorld()
 {
  MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().visible().get();
  TextView txt = (TextView)activity.findViewById(R.id.txt);
  
  ShadowTextView shadowTextView = Robolectric.shadowOf(txt);

  assertEquals(shadowTextView.innerText(),activity.getString(R.string.hello_world));

 }

使用 shadowTextView 的 innerText 來判斷 字串是否相同
最後附上 sample code

2014年1月21日 星期二

Android 開發 (十七) 如何動態加入view

在作專案時,總是有必須動態增加view的情形
在這邊示範一個簡單的範例


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

   RadioButton btn  = new RadioButton(this);
   LinearLayout.LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
   btn.setLayoutParams(params);
   rootLayout.addView(btn);
  }

這是簡單的方式將RadioButton 加入 rootLayout裡  其中  rootLayout為RadioGroup

下圖為結果

上面是較為簡單的範例  假設今天我們必須customize我們的layout
就必須取得inflater取得inflater的方式有兩種

LayoutInflater inflater = LayoutInflater.from(this);
LayoutInflater LayoutInflater =  
             (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

在取得 inflater之後就可以使用 findviewbyId去取得單一的view
下面有簡單的範例供參考

  for(int i=0; i<10;i++){
   LayoutInflater LayoutInflater =  
             (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   View view = LayoutInflater.inflate(R.layout.custom_layout, null);
   TextView txt = (TextView)view.findViewById(R.id.txtview);
   txt.setText(String.valueOf(i));
   rootLayout.addView(view);
  }

上面的sample code在取得 inflater之後,使用 findviewbyId取得textView,並且設定了text
這樣就完成了客製化


最後附上 sample code