2013年10月31日 星期四

Android 開發 (四) custom Gridview Selector

在設計app 時通常不會使用default的設計
所以客製化的技巧也成為重要的課題
今天寫的是讓使用者在點選 GridView時
可以看到顏色的feedback



Main

Main 只是簡單的將 gridview 做資料的設定
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView gv = (GridView) this.findViewById(R.id.gridview);
String[] item = new String[] {"1", "2","3","4","5","6","7","8","9"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                      android.R.layout.simple_expandable_list_item_1,item);
gv.setAdapter(adapter);
}


activity_main

activity_main 在一個relativelayout裡放一個Gridview 並限定每行只能有三個item
其中android:listSelector="@drawable/customselect" 我們使用了我們客製化的selector


<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <GridView
        android:id="@+id/gridview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:listSelector="@drawable/customselect"
        android:numColumns="3"/>
</RelativeLayout>


customselect.xml

在customselect中 我們設定了在 pressed state時  更改顏色

<?xml version="1.0" encoding="UTF-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape > <solid android:color="#77007777" />   </shape>
        </item>
</selector>

結果圖示


尚未做點選的圖案





點選時的圖片


2013年10月25日 星期五

CleanCode 讀書筆記 (五) 錯誤處理II 從呼叫者角度定義例外類別

先看看下面的例子
public void GetContain()
{
  try{
      str.contains("KeyWord");
  catch(NullPointerException ex)
  {
       throw new NullPointerException ();
  }
 }
 public String executeHttpGet(String Url) {
  HttpClient httpClient = new DefaultHttpClient();
  String result="";
  try{
   HttpResponse response = httpClient.execute(request);
   if(response.getStatusLine().getStatusCode()==HttpURLConnection.HTTP_OK)
   {
      result = EntityUtils.toString(response.getEntity());    
   }
  }
  catch(NullPointerException ex)
  {
       throw new NullPointerException ();
  }
  finally
  {
       httpClient.getConnectionManager().shutdown();
  }
  return result;   
 }

以上的code 在出現例外時會丟出 NullPointerException 
然而這樣子的寫法使用者實際上並不知道發生甚麼事情
到底NullPointerException是由誰丟出來的呢?
 比較好的寫法應該像第二個範例


public void GetContain()
 {
  try{
        str.contains("KeyWord");
  catch(NullPointerException ex)
  {
       throw new DataAccessException("Get Contain Fail",ex);
  }

 }
 public String executeHttpGet(String Url) {
  HttpClient httpClient = new DefaultHttpClient();
  String result="";
  try{  
   HttpResponse response = httpClient.execute(request);
   if(response.getStatusLine().getStatusCode()==HttpURLConnection.HTTP_OK)
   {
       result = EntityUtils.toString(response.getEntity());    
   }
  }
  catch(NullPointerException ex)
  {
      throw new DataAccessException("Http Request Fail",ex);
  }
  finally
  {
   httpClient.getConnectionManager().shutdown();
  }

  return result;   
 } 

以上的code可以很清楚的知道例外是由誰產生
這樣的好處 讓我們將原本無法理解的exception變成我們所能理解exception 
讓我們知道確切的問題點在哪

接著下面的例子是將第三方API可能產生的exception 利用單一例外處理使得我們更好管理
將所有的例外使用單一的例外(DataAccessException)去處理
由於第三方的API 出現例外時不管錯誤為何
通常處理的方式都會類似
所以可以使用這樣的方式來處理
可以將該 API 包成一個class 專門處理該API

 
ThirdPartyHandler{
    private ThirdPartyAPI ThirdParty;
    public ThirdPartyHandler(ThirdParty)
    {
       this.ThirdParty = ThirdParty;
    }
    public String executeThirdPartyAPI(String Url) {
    try{  
        result = ThirdParty.Execute();
    }
    catch(NullPointerException ex)
    {
       throw new DataAccessException(ex);
    }
    catch(IOException ex)
    {
       throw new DataAccessException(ex);
    }
    finally
    {
      ...
    }
    return result;   
   }
}

從呼叫者角度定義例外類別告訴我們
利用封裝自定義 exception或者是增加message 讓我們更輕易的管理例外

2013年10月23日 星期三

Android 開發 (三) 擷取圖片的一半

 以下的程式示範如何在android裡  只顯示圖片的左半部


 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  ImageView myImageView = (ImageView)findViewById(R.id.imageview);
  Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.image1);  
  Bitmap resizedbitmap = Bitmap.createBitmap(bmp , 0, 0,bmp.getWidth()/2 , bmp.getHeight());
  myImageView.setImageBitmap(resizedbitmap);

        ImageView myImageView2 = (ImageView)findViewById(R.id.imageview2);
        myImageView2.setImageResource(R.drawable.image1);
        
 }
利用 BitmapFactory.decodeResource 取得 Bitmp之後
再使用  Bitmap.createBitmap 給定  start_x , start_y ,width, height
取得希望得到的Bitmap範圍

2013年10月21日 星期一

OOAD原則 (三) dependency injection 相依性注入

相依性注入(DI)是一個能夠幫助我們解耦合的方法
舉例來說 Strategy Pattern 就有用到這個idea
    abstract class Sorting
    {
        abstract public void Process();
    }
    class SortAlgorithm
    {
        private Sorting algorithm;
        public SortAlgorithm(Sorting algorithm)
        {
            this.algorithm = algorithm;
        }
        public void Sort()
        {
            algorithm.Process();
        }
    }
    class QuickSort : Sorting
    {
        public override void Process()
        {
            System.Diagnostics.Debug.Write("QickSort Algorithm");
        }
    }
    class MergeSort : Sorting
    {
        public override void Process()
        {
            System.Diagnostics.Debug.Write("MergeSort Algorithm");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Sorting quicksort = new QuickSort();
            SortAlgorithm sortalgorithm = new SortAlgorithm(new QuickSort());
            sortalgorithm.Sort();
            sortalgorithm = new SortAlgorithm(new MergeSort());
            sortalgorithm.Sort();
        }
    }


從例子來說   Sorting 真正的實體是由  QuickSort 或 MergeSort來決定的
這部分就是 DI的精神  這樣子寫就可以讓 SortAlgorithm與 QuickSort 以及 MergeSort 解耦合
如果之後有需要做SortAlgorithm的測試時  可以 丟入一個  寫死的演算法 TestSort  回傳固定的排序  這樣就可以測試 SortAlgorithm是否有問題

OOAD原則 (二) Single Responsibility Principle 單一責任原則

SingleResponsibilityPrinciple主張 一個類別只有一個被修改的理由  一個類別只有一個職責
當一個類別可以做很多事情時  我們會稱他為God class  
通常這種類別的重覆使用性很低  因為他會跟很多其他的類別耦合  很難重覆使用

    class GodClass
    {
        private bool IsTurnOnTV = false;
        private bool IsTurnOnRadio = false;
        private bool IsTurnOnLight = false;
        public void TurnOnTV()
        {
            IsTurnOnTV = true;
            Console.WriteLine("TurnOnTV");
        }
        public void TurnOffTV()
        {
            IsTurnOnTV = false;
            Console.WriteLine("TurnOffTV");
        }
        public void TurnOnRadio()
        {
            IsTurnOnRadio = true;
            Console.WriteLine("TurnOnRadio");
        }
        public void TurnOffRadio()
        {
            IsTurnOnRadio = false;
            Console.WriteLine("TurnOffRadio");
        }

        public void TurnOnLight()
        {
            IsTurnOnLight = true; ;
            Console.WriteLine("TurnOnLight");
        }
        public void TurnOffLight()
        {
            IsTurnOnLight = false;
            Console.WriteLine("TurnOffLight");
        }

        public void Execute(string command)
        {
            switch (command)
            {
                case "TV":
                    if (IsTurnOnTV)
                        TurnOffTV();
                    else
                        TurnOnTV();
                    break;
                case "Light":
                    if(IsTurnOnLight)
                        TurnOffLight();
                    else
                        TurnOnLight();
                    break;
                case "Radio":
                    if (IsTurnOnRadio)
                        TurnOffRadio();
                    else
                        TurnOnRadio();
                    break;
                default:
                    throw new Exception();
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            GodClass godclass = new GodClass();
            godclass.Execute("Light");
            godclass.Execute("Light");
            Console.ReadKey();
        }
    }

上面是一個 god 類別的例子  他可以做 TV的開關  Light的開關 以及  Radio的開關
簡單的說就是甚麼都可以做  這樣寫好像不錯  所有控制的項目都整理在一個類別裡
但是  當有新的功能要要加入時  就得對execute的程式做修改  這樣就違反了OpenClosePrinciple
然後當新的需求不斷進來  最後  這個類別就會變成一個龐大而且充滿flag的類別
這樣的程式是非常難以維護的
但是  我們可以用下面的方式來做修改


  abstract class Command
    {
        public abstract void Execute();
    }
    class LightCommand : Command
    {
        private bool IsTurnOnLight = false;
        public void TurnOnLight()
        {
            IsTurnOnLight = true; ;
            Console.WriteLine("TurnOnLight");
        }
        public void TurnOffLight()
        {
            IsTurnOnLight = false;
            Console.WriteLine("TurnOffLight");
        }
        public override void Execute()
        {
            if (IsTurnOnLight)
                TurnOffLight();
            else
                TurnOnLight();
        }
    }

    class TVCommand : Command
    {
        private bool IsTurnOnTV = false;
        public void TurnOnTV()
        {
            IsTurnOnTV = true;
            Console.WriteLine("TurnOnTV");
        }
        public void TurnOffTV()
        {
            IsTurnOnTV = false;
            Console.WriteLine("TurnOffTV");
        }
        public override void Execute()
        {
            if (IsTurnOnTV)
                TurnOffTV();
            else
                TurnOnTV();
        }
    }

    class RadioCommand : Command
    {
        private bool IsTurnOnRadio = false;
        public void TurnOnRadio()
        {
            IsTurnOnRadio = true;
            Console.WriteLine("TurnOnRadio");
        }
        public void TurnOffRadio()
        {
            IsTurnOnRadio = false;
            Console.WriteLine("TurnOffRadio");
        }
        public override void Execute()
        {
            if (IsTurnOnRadio)
                TurnOffRadio();
            else
                TurnOnRadio();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Command LightCommand = new LightCommand();
            LightCommand.Execute();
            LightCommand.Execute();
            Console.ReadKey();
        }
    }
基本上這兩段程式碼做的事情是一樣的
但是上面這一段程式碼
當新的需求進來時   例如我想新增電腦開關
不論我在新的類別裡面修改或新增了多少東西
並不會影響到其他已經寫好的類別
對維護者來說  這樣的類別也是相對好維護的  flag少而且執行的功能明確
這就是把責任切開的好處

2013年10月17日 星期四

CleanCode 讀書筆記 (四) 邊界

邊界主要是描述使用第三方API時如何想辦法
將API與我們內部的程式分隔開來   減低耦合
裡面有提到可以使用 Adapter  (轉接器)
Adapter是系統 與 API 之間的橋梁
使得API有所更動時  不會影響到系統  只需要在 Adapter上做修改即可

以下兩個範例  廠商在開發初期出版了  FactoryAPI_0
以及過了一陣子出版了新版  FactoryAPI_1
在兩版之間  裡面有一個method做了修正
如果沒有使用Adapter  會使得主程式必須做修正  修正後也可能會衍生出bug
但是使用Adapter的話  由於修改都在一個class內   相對來說比較不會衍生出新問題

廠商舊版FactoryAPI_0


    public class FactoryAPI_0
    {
        public void Make()
        {
            System.Diagnostics.Debug.Write("Make");
        }
    }

    public class FactoryAdapter
    {
        private FactoryAPI_0 ApI;
        public FactoryAdapter(FactoryAPI_0 API)
        {
            this.ApI = API;
        }
        public void Process()
        {
            ApI.Make();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FactoryAPI_0 api = new FactoryAPI_0();
            FactoryAdapter adapter = new FactoryAdapter(api);
            adapter.Process();
            Console.Read();

        }
    }



廠商新版 FactoryAPI_1


    public class FactoryAPI_1
    {
        public void Execute()
        {
            System.Diagnostics.Debug.Write("Make");
        }
    }
    public class FactoryAdapter
    {
        private FactoryAPI_1 ApI;
        public FactoryAdapter(FactoryAPI_1 API)
        {
            this.ApI = API;
        }
        public void Process()
        {
            ApI.Execute();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FactoryAPI_0 api = new FactoryAPI_0();
            FactoryAdapter adapter = new FactoryAdapter(api);
            adapter.Process();
            Console.Read();

        }
    }
由上面兩個範例可以發現    主程式並沒有被更動到
所有的更動都在 class 內部
造成的影響也都在class內   主程式成功的與API 分開

簡潔的程式邊界
當改變來臨時
好的設計能適應變化
不好的設計必須重新改寫或投入大量時間做修正

API 可以用封裝的方式  或者使用adapter
使API 被修正時  我們只需要做及少的修正

2013年10月16日 星期三

CleanCode 讀書筆記 (三) 錯誤處理I

裡面提到了一些例外的處理方式
其中有一點我覺得蠻重要的
不要回傳null  
與其回傳null不如回傳一個special case
這樣呼叫這個class的人就不需要去做 if(xx ==null)的判斷
也不會出現 NullPointerException  以下是範例


    abstract class EmployeeData
    {
        protected string name;
        protected string birthday;
        protected string address;
        public string GetName()
        {
            return name;
        }
    }
    class Ted : EmployeeData
    {
        public Ted()
        {
            name = "Ted";
            birthday = "1009";
            address = "Taipei";
        }
    }
    class NullEmployeeData : EmployeeData
    {
        public NullEmployeeData()
        {
            name = "";
            birthday = "";
            address = "";
        }
    }
    class EmployeeList
    {
        public EmployeeData GetData(string name)
        {
            switch (name)
            {
                case "Ted":
                    return new Ted();
                default:
                    return null;
            }

        }
    }
    class EmplyeeListBetter
    {
        public EmployeeData GetData(string name)
        {
            switch (name)
            {
                case "Ted":
                    return new Ted();
                default:
                    return new NullEmployeeData();
            }

        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            EmployeeData employeedata;
            EmployeeList badEmployeeList = new EmployeeList();

            employeedata = badEmployeeList.GetData("Marry");
            if (employeedata!=null)
                employeedata.GetName();


            EmplyeeListBetter betterEmployeeList = new EmplyeeListBetter();
            employeedata = betterEmployeeList.GetData("Marry");
            employeedata.GetName();
            Console.ReadKey();
        }
    }

上面的範例說明了當 user想要去Get Marry的資料時  系統內並沒有這個資料
badEmployeeList  return 了 null
然而 betterEmployeeList 回傳了  NullEmployeeData這個物件
這樣寫的好處是就算user沒有做  if (employeedata!=null) 這個判斷式
也不需要擔心 NullPointerException 的發生  系統還是可以繼續 run 下去