2017年7月29日 星期六

Android:使用JackSon函式庫解析Json

在Android裡要解析伺服端回傳的Json格式資料,除了使用內建在android studio裡的Json.org外,還可以使用第三方的函式庫,像是Gson或JackSon,JackSon是目前開放原碼中有著高效率特色的函式庫之一,常用在許多的應用程式中。


要使用JackSon解析Json資料時,需要先導入JackSon函式庫,下載網址 :
http://www.java2s.com/Code/Jar/j/Downloadjacksonall199jar.htm

下面的範例以GoogleBook的api為範例:
https://www.googleapis.com/books/v1/volumes?q=java (q後面為書名)



伺服器回傳的Json :
紅色框框為要取得的資訊title(書名)與subtitle(副標題)
PS:subTitle並不是每本書都會有
下面這本書就有subtitle了


為了好解析Json裡的資料,可以畫一張類別圖,這樣就可以知道我們接下來要準備建立哪些類別和底下有哪些屬性:

*表示多值,也就是items底下有多筆資料,像是有kind、id、etag、selfLink、volumeInfo,在Json格式裡會以[ ] 陣列表示,另外在Json格式裡{ } 表示物件



程式碼:
activity_main.xml:

<LinearLayout 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"
    android:orientation="vertical" >
    
    <TextView
        android:id="@+id/txt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="書名" />
    
    <EditText 
        android:id="@+id/edt"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        />
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="search"
        />
    
</LinearLayout>

MainActivity.java:

public class MainActivity extends Activity {
 
    private Button btn;
    private EditText edt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        findviewbyid();
        
    }
    private void findviewbyid(){
        btn = (Button)findViewById(R.id.btn);
        edt = (EditText)findViewById(R.id.edt);
        btn.setOnClickListener(btnlistener);
    }
    private View.OnClickListener btnlistener = new View.OnClickListener() {
  
  @Override
  public void onClick(View arg0) {

//   Loose Coupling
//   Intent it = new Intent("com.mchen.Activity2");
//   it.putExtra("bookName", edt.getText().toString());

     Uri uri = Uri.parse("Tom:"+edt.getText().toString());  //使用自訂的Scheme
     Intent it = new Intent(Intent.ACTION_VIEW,uri);   //跳頁到DisplayBooks
     startActivity(it);
  }
 };
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    
}

BookInfo2.java:

import org.codehaus.jackson.annotate.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class BookInfo2 {
 
 private int totalItems;
 private List<items> items = new ArrayList<items>();
 
 public BookInfo2(){
  
 }
 public BookInfo2(int totalItems,List<items> items){
  this.totalItems = totalItems;
  this.items = items;
 }
 
 public int getTotalItems() {
  return totalItems;
 }
 public void setTotalItems(int totalItems) {
  this.totalItems = totalItems;
 }
 public List<items> getItems() {
  return items;
 }
 public void setItems(List<items> items) {
  this.items = items;
 }
 
}

items.java:

import org.codehaus.jackson.annotate.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class items {
 
 private volumeInfo volumeInfo;

 public items(){
  
 }
 
 public items(volumeInfo volumeInfo){
  this.volumeInfo = volumeInfo;
 }

 public volumeInfo getVolumeInfo() {
  return volumeInfo;
 }

 public void setVolumeInfo(volumeInfo volumeInfo) {
  this.volumeInfo = volumeInfo;
 }

}

volumeInfo.java:

import org.codehaus.jackson.annotate.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class volumeInfo {
 
 private String subtitle;
 private String title;
 public String getSubtitle() {
  
  return subtitle;
  
 }
 public void setSubtitle(String subtitle) {
  this.subtitle = subtitle;
 }
 public String getTitle() {
  return title;
 }
 public void setTitle(String title) {
  this.title = title;
 }

}

book.xml:

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

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:dividerHeight="5dp"
        android:id="@+id/listview">
    </ListView>

</LinearLayout>

row.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/txtTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/txtSubTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />
    

</LinearLayout>

DisplayBooks.java:

public class DisplayBooks extends Activity {

 private ListView listView;

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.book);

        listView = (ListView)findViewById(R.id.listview);
        Log.i("mutin","action-"+this.getIntent().getAction());
        Log.i("mutin", "data-"+this.getIntent().getData().getSchemeSpecificPart());

        final String param = this.getIntent().getData().getSchemeSpecificPart();
        int cpuCount = Runtime.getRuntime().availableProcessors();  //取得可以建立Thread的最大數量
        ExecutorService es = Executors.newFixedThreadPool(cpuCount);   //建立Thread
        Future<String> f = es.submit(new Callable<String>(){      //可以取得Callable回傳結果

   @Override
   public String call() throws Exception {
    // TODO Auto-generated method stub
    URL url = new URL("https://www.googleapis.com/books/v1/volumes?q="+param);
    java.net.URLConnection con = url.openConnection();
    BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
    String data = null;
    StringBuffer result  = new StringBuffer();
    while((data=br.readLine())!=null){
     result.append(data);
    }

    return result.toString();
   }
         
        });
        try{
         String result = f.get();
         es.shutdown();

   //產生Jackson函式庫負責解析的ObjectMapper類別
         ObjectMapper mapper = new ObjectMapper();

   //呼叫ObjectMapper的readValue方法進行Json格式轉換為Java資料的工作
         BookInfo2 bi = mapper.readValue(result,BookInfo2.class);
         HashMap<String,String> map;
         ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
         for(items item:bi.getItems()){
          Log.i("mutin", "Title="+item.getVolumeInfo().getTitle());
          Log.i("mutin", "SubTitle="+item.getVolumeInfo().getSubtitle());
          
          map = new HashMap<String,String>();
                map.put("title", "Title:"+item.getVolumeInfo().getTitle());
          map.put("subTitle", "SubTitle:" + item.getVolumeInfo().getSubtitle());
                list.add(map);
         }
         
         SimpleAdapter adapter = new SimpleAdapter(this,list,R.layout.row,new String[]{"title","subTitle"},new int[]{R.id.txtTitle,R.id.txtSubTitle});
            listView.setAdapter(adapter);
         
        }catch(Exception ex){
         ex.printStackTrace();
        }
        
 }

}
PS:用HightLight上色程式碼,格式會跑掉,請見諒

執行畫面:

2017年7月25日 星期二

Android:動畫

Android目前支援3種類型的動畫:
1.影格動畫(Frame Animation)
2.補間動畫(View Animation)
3.屬性動畫(Property Animation)


(1)Frame Animation(影格動畫)類似gif動畫一樣是一連串的drawable序列組成,優點是使用簡單方便、缺點是需要事先準備好每一幀圖片。

(2)View Animation(視圖動畫)視圖動畫比較簡單,只能應用於各種View,可以做一些位置、大小、旋轉和透明度的簡單轉變。僅需定義開始與結束的關鍵幀,而變化的中間幀由系統補上,可以達到大部分的動畫需求。

(3)Property Animation(屬性動畫)是3.0後推出的動畫,優點是使用簡單、降低實現的複雜度、直接更改對象的屬性、幾乎可適用於任何對象而僅非View類,缺點是需要3.0以上的API支持


本篇介紹View Animation

可以通過xml檔定義,xml檔放於res/anim/目錄下,根項目可以為:<alpha>, <scale>, <translate>, <rotate>, 或者<set>。其中,<set>標籤定義的是動畫集,它可以包含多個其他標籤

<alpha>
可以定義動畫的透明度,也就是可以實現淡入淡出的效果
  • android:duration 動畫從開始到結束持續的時長,單位為毫秒 
  • android:fromAlpha 動畫開始時的透明度,0.0為全透明,1.0為不透明,默認為1.0 
  • android:toAlpha 動畫結束時的透明度,0.0為全透明,1.0為不透明,默認為1.0
動畫的xml檔:

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="0.0"
    android:toAlpha="1.0" />
將動畫添加到view上:

view.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in));

<scale>
可以定義縮放的視圖動畫

  • android:fromXScale 表示沿X軸縮放的起始比例
  • android:toXScale 表示沿X軸縮放的結束比例
  • android:fromYScale 表示沿Y軸縮放的起始比例
  • android:toYScale 表示沿Y軸縮放的結束比例

          以上四個屬性,0.0表示縮放到沒有,1.0表示正常無縮放,小於1.0表示收縮,大於1.0表示放大

  • android:pivotX 縮放時的固定不變的X座標,一般用百分比表示,0%表示左邊緣,100%表示右邊緣
  • android:pivotY 縮放時的固定不變的Y座標,一般用百分比表示,0%表示頂部邊緣,100%表示底部邊緣

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXScale="1.0"
    android:fromYScale="1.0"
    android:pivotX="0%"
    android:pivotY="100%"
    android:toXScale="1.5"
    android:toYScale="1.5" />

<translate>
可以定義位移的動畫效果

  • android:interpolator 表示動畫加速器,可設定屬性有accelerate_interpolator(加速)、decelerate_interpolator(減速)和accelerate_decelerate_interpolator(加速減速)
  • android:fromXDelta 動畫起始位置的水平坐標
  • android:toXDelta 動畫結束位置的水平坐標
  • android:fromYDelta 動畫起始位置的垂直坐標
  • android:toYDelta 動畫結束位置的垂直坐標
  • android:duration 動畫的持續時間,單位是毫秒

座標的值可以有三種格式:從-100到100;以"%"結束,表示相對於View本身的百分比位置;如果以"%p"結束,表示相對于View的父View的百分比位置;如果沒有任何尾碼,表示相對於View本身具體的圖元值。

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromXDelta="-100%"
    android:fromYDelta="0"
    android:toXDelta="100%p"
    android:toYDelta="0" />

<rotate>
可以定義旋轉的動畫

  • android:fromDegrees 表示旋轉的起始角度
  • android:toDegrees 表示旋轉的結束角度
  • android:repeatCount 設定旋轉的次數,0表示不重複,1表示會多旋轉一次,infinite或-1表示一直旋轉
  • android:repeatMode 設定重複的模式,有restart與reverse,預設是restart,該屬性只有當repeatCount設定大於0或infinite時才有作用

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%" />

<set>
可以將多個動畫組合成一個動畫集

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate 
        android:fromYDelta="100%p" 
        android:toYDelta="0"
        android:duration="2000"/>
    <alpha 
        android:fromAlpha="0.0" 
        android:toAlpha="1.0"
        android:duration="2000" />
</set>

2017年7月18日 星期二

Android:Fragment(片段)

當還沒有Fragment功能出來以前,必須使用很多個Activity來轉換到另一個Activity的方式來設計功能,這在手機上使用來看,感覺起來沒什麼問題,但是平板出來之後,畫面變大,一個畫面可以容納更多的元件,所以為了讓Android UI在平板上可以有更好的展示效果,Android在3.0時加入了Fragment功能。



Fragment片段是Android提供的一個畫面區塊,可以將Fragment放在一個Activity中,Fragment可以說是Activity的子畫面。

開發上使用Fragment的好處是:

  • 有自己的生命週期
  • 可以重複利用降低開發成本
  • 一個Fragment在手機和平板上可以有不同的使用者體驗


Fragment與Activity的生命週期:


Fragment的生命週期必須依附Activity的生命週期,意思是說,當 Activity 暫停時,其中的所有片段也會一併暫停;而當 Activity 遭到刪除時,所有片段也會一併刪除。 不過,當 Activity 執行時 (該 Activity 會處於繼續進行生命週期狀態),您可以個別操縱所有片段,例如新增或移除片段。

Fragment動態佈署主要分為動態新增(add)動態置換(replace兩種:

  • replace是每次呼叫,不管有沒有建立過fragment,都會創建一個新的Fragment,所以生命週期也會重跑一遍,如果使用在需要經常更換fragment的時候,非常消耗資源。
  • add則是可以搭配hide()與show()來建立Fragment,當要切換Fragment時,可以hide()當前的fragment,並且add()和show()新的fragment


動態置換(replace)程式碼:

activity_main.xml:

<?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">

    <LinearLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="20dp" />

    <RadioGroup
        android:id="@+id/id_radioGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/id_one"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@mipmap/ic_launcher"
            android:gravity="center_horizontal"
            android:text="page1"
            android:onClick="onClick"/>


        <RadioButton
            android:id="@+id/id_two"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@mipmap/ic_launcher"
            android:gravity="center_horizontal"
            android:text="page2"
            android:onClick="onClick"/>

        <RadioButton
            android:id="@+id/id_three"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@mipmap/ic_launcher"
            android:gravity="center_horizontal"
            android:text="page3"
            android:onClick="onClick"/>

        <RadioButton
            android:id="@+id/id_four"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@mipmap/ic_launcher"
            android:gravity="center_horizontal"
            android:text="page4"
            android:onClick="onClick"/>

    </RadioGroup>
</RelativeLayout>


MainActivity.java:

public class MainActivity extends FragmentActivity {
    private RadioButton id_one,id_two,id_three,id_four;
    private int page = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        findviewbyid();
        changeFragment(DetailsFragment.newInstance(page));

    }
    public void onClick(View view){
        switch (view.getId()){
            case R.id.id_one:
                changeFragment(DetailsFragment.newInstance(1));
                break;
            case R.id.id_two:
                changeFragment(DetailsFragment.newInstance(2));
                break;
            case R.id.id_three:
                changeFragment(DetailsFragment.newInstance(3));
                break;
            case R.id.id_four:
                changeFragment(DetailsFragment.newInstance(4));
                break;
        }
    }
    private void findviewbyid(){
        id_one = (RadioButton)findViewById(R.id.id_one);
        id_two = (RadioButton)findViewById(R.id.id_two);
        id_three = (RadioButton)findViewById(R.id.id_three);
        id_four = (RadioButton)findViewById(R.id.id_four);
    }

    private void changeFragment(Fragment f) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.fragment_container, f);
        transaction.commitAllowingStateLoss();
    }
    protected void onStart(){
        super.onStart();
        Log.i("tag","Activity onStart()");
    }
    protected void onResume(){
        super.onResume();
        Log.i("tag","Activity onResume()");
    }
}

fragment_tmp.xml(fragment內容的佈局文件):

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/text_view"
            android:text="fragment"
            android:gravity="center"
            android:textSize="20sp"/>

</LinearLayout>

DetailFragment.java:

public class DetailsFragment extends Fragment {


    private View v;
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.i("tag","Fragment onCreateView()");
        v = inflater.inflate(R.layout.fragment_tmp, container, false);
        TextView text = (TextView)v.findViewById(R.id.text_view);
        text.setText("Page" + getShownIndex());
        return v;
    }


}

執行結果:


動態新增(add)程式碼:

activity_main.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="20dp" />

    <RadioGroup
        android:id="@+id/id_radioGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/id_one"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@mipmap/ic_launcher"
            android:gravity="center_horizontal"
            android:text="page1"
            android:onClick="onClick"/>


        <RadioButton
            android:id="@+id/id_two"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@mipmap/ic_launcher"
            android:gravity="center_horizontal"
            android:text="page2"
            android:onClick="onClick"/>

    </RadioGroup>
</RelativeLayout>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private RadioButton id_one,id_two;
    private Fragment1 mFragment01;
    private Fragment2 mFragment02;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findviewbyid();
        mFragment01 = new Fragment1();
        mFragment02 = new Fragment2();
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.fragment_container, mFragment01,"TAG-mFragment01");
        transaction.commitAllowingStateLoss();

    }

    private void findviewbyid(){
        id_one = (RadioButton)findViewById(R.id.id_one);
        id_two = (RadioButton)findViewById(R.id.id_two);
    }
    public void onClick(View view){
        switch (view.getId()){
            case R.id.id_one:
                chaangefragment(mFragment01,"TAG-mFragment01");
                break;
            case R.id.id_two:
                chaangefragment(mFragment02,"TAG-mFragment02");
                break;
        }
    }
    public void chaangefragment(Fragment fragment,String tag){
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        FragmentManager fragmentManager = MainActivity.this.getSupportFragmentManager();
        List fragments = fragmentManager.getFragments();
        Fragment tmp=null;
        for (Fragment f: fragments){
            if (f!=null && f.isVisible()) {
                System.out.println("hide"+f.getTag());
                tmp=f;
            }
        }
//        Fragment fragment = getSupportFragmentManager().findFragmentByTag("TAG-mFragment01");
//        System.out.println(fragment);
        if (fragment.isAdded()){
            transaction.hide(tmp).show(fragment).commit();
        }else {
            transaction.hide(tmp).add(R.id.fragment_container,fragment,tag).commit();
        }
    }
}

fragment_layout.xml:

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/txt"
        android:text="fragment"/>

</LinearLayout>

fragment1.java:

public class Fragment1 extends Fragment {

    private TextView txt;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.i("tag","Fragment1  onCreateView");
        return inflater.inflate(R.layout.fragment_layout, container, false);

    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.i("tag","Fragment1  onActivityCreated");
        txt = (TextView)getView().findViewById(R.id.txt);
        txt.setText("fragment1");
    }


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.i("tag","Fragment1  onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("tag","Fragment1  onCreate");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.i("tag","Fragment1  onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i("tag","Fragment1  onResume");
    }
}

fragment2.java:

public class Fragment2 extends Fragment{

    private TextView txt;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.i("tag","Fragment2  onCreateView");
        return inflater.inflate(R.layout.fragment_layout, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.i("tag","Fragment2  onActivityCreated");
        txt = (TextView)getView().findViewById(R.id.txt);
        txt.setText("fragment2");
    }
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.i("tag","Fragment2  onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("tag","Fragment2  onCreate");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.i("tag","Fragment2  onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i("tag","Fragment2  onResume");
    }
}

執行順續page1(起始) -> page2 -> page1 -> page2
執行結果:

可以看到fragment1和fragment2個別被建立1次後,之後再呼叫時就不會再被建立新的Fragment了。





2017年7月16日 星期日

Android:使用Handler與Thread更新UI

在android中系统不允許在非Main Thread更新UI。當我們在非主線程做了耗時操作後,需要去更新UI的時候,我們就需要使用Handler來執行更新操作。

範例用一個線程,在線程裡不斷循環,線程每休眠1s,sendMessageHandle,在handleMessage方法裡更新UI線程即TextView的内容,讓TextView不斷更新時間。

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="com.example.user.service.MainActivity"
    android:orientation="vertical">
  
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/txt"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private TextView txt;
    private static final int msgKey1 = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txt = (TextView)findViewById(R.id.txt);

        Thread t = new Thread(runnable);
        t.start();
    }
    
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            do{
                try {
                    Thread.sleep(1000);
                    Message msg = new Message();
                    msg.what = msgKey1;
                    mHandler.sendMessage(msg);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }while (true);
        }
    };
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case msgKey1:
                    long time = System.currentTimeMillis();
                    Date date = new Date(time);
                    SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 EEE");
                    txt.setText(format.format(date));
                    break;
                default:
                    break;
            }
        }
    };
}

要不斷更新UI除了使用Handler外,還可以使用runOnUiThread()這個方法來更新UI

public class MainActivity extends AppCompatActivity {

    private TextView txt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txt = (TextView)findViewById(R.id.txt);

        Thread t = new Thread(runnable);
        t.start();


    }

    private Runnable runnable = new Runnable() {
    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(1000);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // update TextView here!
                        long time = System.currentTimeMillis();
                        Date date = new Date(time);
                        SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 EEE");
                        txt.setText(format.format(date));
                    }
                });
            }
        } catch (InterruptedException e) {
        }
    }
    };

2017年7月15日 星期六

Android:MVC模式

MVC模式已經被廣泛使用在各種的程式(桌面程式、Web程式、行動程式等),其中M代表模型(Model也就是資料),V代表視圖(View,也就是介面),C代表控制器(Controller,控制M和V),MVC主要的目的就是使程式中的資料和介面分離,這樣當更換視圖或模型時可以不影響對方,更有利於程式的維護。

MVC模式基本的原理,就是透過Controller連接View和Model,也就是當View中的資料變化時(如Listview要刪除某個清單項),會通知Controller,而非直接通知Model。這時當Controller接收到View的通知後,會在Model中才取對應的動作(如刪除資料庫中的某筆記錄)。

在Android中使用MVC的元件非常多,如所有的清單元件(Listview、Spinner、GridView),其中V就代表這些元件,M則代表各種資料來源,C則代表Adapter類別。

Android:Activity的四種啟動模式

在多Activity開發中,有可能是自己應用之間的Activity跳轉,或者夾帶其他應用的可復用Activity。

可能會希望一個Activity跳轉到原來某個Activity實例,而不是產生大量重複的Activity。

這需要為Activity配置特定的啟動模式,而不是使用默認的啟動模式。


Activity有四種啟動模式:
  • standard(預設)
  • singleTop
  • singleTask
  • singleInstance
設置的位置在AndroidManifest.xml文件中的<activity>標籤裡設定android:launchMode屬性:

<activity android:name=".MainActivity"
          android:launchMode="standard">

或是透過Intent來設定
Intent intent = new Intent(this, B.class);   
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);   
startActivity(intent);  

通常當一個Activity1啟動時,會把該Activity1放入一個Task裡,如果當有Activity2被啟動時,也會被放入Activity1所在的Task,以堆疊的方式來存放Activity,然後當使用者按下返回健或程式執行finish()時,該Activity就會從Task裡移除

然而有的時候我們的需求可能是,當產生新的Activity時,把它上入新的Task裡;或者希望將已存在的Activtiy拿到Task頂部來使用,而不是產生一個新的。

下面介紹4種Activity的啟動模式:


1. standard模式:

    首先說standard模式,也就是默認模式,不需要配置launchMode。
這種模式下,如果重複創建同一個Activity實例,intent將發送給新的實例。即同一個Activity有多個


2.singleTop模式:

    singleTop和standard模式,都會將intent發送新的實例
如果Task的頂部已經存在了該Activity,那麼,它便不會重新創建,而是調用 onNewIntent。如果,該Activity存在,但不是在頂部,那麼該Activity依然要重新創建。


3.singleTask模式: 

    如果在Task已經有該Activity的實例,就重用該實例(會調用實例的onNewIntent())。重用時,會讓該實例回到Task頂部,因此在它上面的實例將會被移除棧。如果Task中不存在該實例,將會創建新的實例放入棧中。


4.singleInstance模式:

     加載該Activity时如果没有實例化,他會創建新的Task后,實例化Activity放入Task,如果已經存在,直接調用 onNewIntent,該Activity的Task中不允許啟動其它的Activity,任何從該Activity動的其他Activity都將被放到其他task中

  • 是否允許多個實例

「standard」和「singleTop」可以被實例化多次,並且是可以存在於不同的task中;這種實例化時一個task可以包括一個activity的多個實例;「singleTask」和」singleInstance」則限制只生成一個實例

  • 是否允許其它activity存在於本task內

「singleInstance」獨占一個task,其它activity不能存在那個task里,如果它啟動了一個新的activity,不管新的activity的launch mode 如何,新的activity都將會到別的task里運行,而另外三種模式,則可以和其它activity共存。

  • 是否每次都生成新實例

「standard」對於每一個啟動Intent都會生成一個activity的新實例;「singleTop」的activity如果在task的棧頂的話,則不生成新的該activity的實例,直接使用棧頂的實例,否則,生成該activity的實例;「singleTask」 如果在棧頂,則接受intent,否則,該intent會被丟棄,但是該task仍會回到前台。 當已經存在的activity實例處理新的intent時候,會調用onNewIntent方法;「singleInstance」是其所在棧的唯一activity,它會每次都被重用。

2017年7月13日 星期四

Android:SQLite資料庫

SQLite是一套開放原始碼的資料函式庫,使用標準的SQL語法,提供無須連線的資料庫的管理系統。

二大優點:

(1) 小而美,該資料庫系統非常小(大約 275k 大小空間),屬於輕量級的資料庫系統,主要是由 C 寫

成,支援大部份標準 SQL 語法,。

(2) 省成本:創建者不僅將其原始碼公開,還無須支付任何費用

SQLite 與其他 Server 等級的資料庫最大的差異點在於,SQLite 直接將資料庫的資料儲存在本機

端,而非 Server 端,而且一個資料庫即儲存成一個檔案。




建立 SQLite 資料庫,必須建立子類別繼承 SQLiteOperHelper 並覆寫 onCreate()onUpgrade(),尤以改寫onCreate() 為重要。

程式碼:

activity_main.xml:

<LinearLayout 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"
    android:orientation="vertical"
    tools:context="tw.com.hjchen.sqlitedemo.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Name"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editName"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Tel"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editTel"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Email"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editEmail"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ID"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editID"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal">
        <Button
            android:id="@+id/btnAdd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Add"
            android:width="100dp"/>
        <Button
            android:id="@+id/btnUpdate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Update"
            android:width="100dp"/>
        <Button
            android:id="@+id/btnDel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Del"
            android:width="100dp"/>
    </LinearLayout>

    <ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="match_parent"
        android:layout_height="120dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:id="@+id/txtResult"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Result"
                android:textSize="20sp"/>
        </LinearLayout>
    </ScrollView>

    <ListView
        android:id="@+id/listData"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
</LinearLayout>

data_item.xml:(listview裡的item)

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

    <TextView
        android:id="@+id/txtId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>
    <TextView
        android:id="@+id/txtName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>
    <TextView
        android:id="@+id/txtTel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>
    <TextView
        android:id="@+id/txtEmail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>

</LinearLayout>

DbConstants.java:

import  android.provider.BaseColumns;

public interface DbConstants extends BaseColumns{
    public static final String TABLE_NAME="friends";
    public static final String NAME="name";
    public static final String TEL="tel";
    public static final String EMAIL="email";
}

DBHelper.java:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import static android.provider.BaseColumns._ID;
import static tw.com.hjchen.sqlitedemo.DbConstants.TABLE_NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.TEL;
import static tw.com.hjchen.sqlitedemo.DbConstants.EMAIL;

public class DBHelper extends SQLiteOpenHelper{
    private final static String DATABASE_NAME="demo.db";  //資料庫檔案名稱
    private final static int DATABASE_VERSION=1;   //資料庫版本
    public DBHelper(Context context){
        super(context,DATABASE_NAME,null,DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db){
        final String INIT_TABLE="create table "+TABLE_NAME+"("+_ID+" integer primary key autoincrement,"+NAME+" char,"+TEL+" char,"+EMAIL+" char)";
        db.execSQL(INIT_TABLE);
    }
    public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion){
        final String DROP_TABLE="drop tabel if exists "+TABLE_NAME;
        db.execSQL(DROP_TABLE);
        onCreate(db);
    }
}

MainActivity.java:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import static android.provider.BaseColumns._ID;
import static tw.com.hjchen.sqlitedemo.DbConstants.TABLE_NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.TEL;
import static tw.com.hjchen.sqlitedemo.DbConstants.EMAIL;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;
public class MainActivity extends AppCompatActivity implements OnClickListener{

    private DBHelper dbHelper;
    private TextView result;
    private ListView listData;
    private EditText editName,editTel,editEmail,editId;
    private Button btnAdd,btnDel,btnUpdatel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViews();
        openDatabase();   //開啟資料庫
        show();          //show scrollview
        showInList();    //show listview

    }
    private void findViews(){
        editName=(EditText)findViewById(R.id.editName);
        editTel=(EditText)findViewById(R.id.editTel);
        editEmail=(EditText)findViewById(R.id.editEmail);
        editId=(EditText)findViewById(R.id.editID);
        btnAdd=(Button)findViewById(R.id.btnAdd);
        btnDel=(Button)findViewById(R.id.btnDel);
        btnUpdatel=(Button)findViewById(R.id.btnUpdate);
        result=(TextView)findViewById(R.id.txtResult);
        listData=(ListView)findViewById(R.id.listData);
        btnAdd.setOnClickListener(this);
        btnDel.setOnClickListener(this);
        btnUpdatel.setOnClickListener(this);
    }
    protected void onDestroy(){
        super.onDestroy();
        closeDatabase();     //關閉資料庫
    }
    private void openDatabase(){
        dbHelper=new DBHelper(this);   //取得DBHelper物件

    }
    private void closeDatabase(){
        dbHelper.close();
    }
    public void onClick(View view){
        switch (view.getId()){
            case R.id.btnAdd:
                add();
                break;
            case R.id.btnDel:
                del();
                break;
            case R.id.btnUpdate:
                update();
                break;
            default:
                break;
        }
        show();
        showInList();
    }
    private void add(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();  //透過dbHelper取得讀取資料庫的SQLiteDatabase物件,可用在新增、修改與刪除
        ContentValues values=new ContentValues();  //建立 ContentValues 物件並呼叫 put(key,value) 儲存欲新增的資料,key 為欄位名稱  value 為對應值。
        values.put(NAME,editName.getText().toString());
        values.put(TEL,editTel.getText().toString());
        values.put(EMAIL,editEmail.getText().toString());
        db.insert(TABLE_NAME,null,values);
        cleanEditText();
    }
    private  void del(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        String id=editId.getText().toString();
        db.delete(TABLE_NAME,_ID+"="+id,null);
        cleanEditText();
    }
    private void update(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        String id=editId.getText().toString();
        ContentValues values=new ContentValues();
        values.put(NAME,editName.getText().toString());
        values.put(TEL,editTel.getText().toString());
        values.put(EMAIL,editEmail.getText().toString());
        db.update(TABLE_NAME,values,_ID+"="+id,null);
        cleanEditText();
    }
    private void cleanEditText(){
        editName.setText("");
        editTel.setText("");
        editEmail.setText("");
        editId.setText("");
    }

    private Cursor getCursor(){
        SQLiteDatabase db=dbHelper.getReadableDatabase();  //透過dbHelper取得讀取資料庫的SQLiteDatabase物件,可用在查詢
        String[] columns={_ID,NAME,TEL,EMAIL};
        Cursor cursor = db.query(TABLE_NAME,columns,null,null,null,null,null);  //查詢所有欄位的資料
        return cursor;
    }

    private void show(){
        Cursor cursor = getCursor();  //取得查詢物件Cursor
        StringBuilder resultData = new StringBuilder("Result:\n");
        while (cursor.moveToNext()){
            int id = cursor.getInt(0);
            String name = cursor.getString(1);
            String tel = cursor.getString(2);
            String email = cursor.getString(3);
            resultData.append(id).append(": ");
            resultData.append(name).append(": ");
            resultData.append(tel).append(": ");
            resultData.append(email).append("\n");
        }
        result.setText(resultData);
    }
    private void showInList(){
        Cursor cursor = getCursor();
        String[] from = {_ID,NAME,TEL,EMAIL};
        int[] to = {R.id.txtId,R.id.txtName,R.id.txtTel,R.id.txtEmail};
        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,R.layout.data_item,cursor,from,to); //SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to)
        listData.setAdapter(adapter);
    }

}

PS:這裡特別說明一下SQLite的查詢功能,SQLiteDatabase類別提供二種查詢功能方法:rawQuery() 與 query()

rawQuery():接受 SQL 查詢語法,可直接將 SQL 查詢語法傳入即可。

query():必須先將 SQL 查詢語法依照 query()的參數來切割。


例:SQL 查詢語法為:Select name,phone from member where name like ?


  •  rawQuery(String sql, String[] selectionArgs)


查詢語法為:

String sql = " Select name,phone from member where name like ? "

String[] selectionArgs = {"%明%"};

Cursor cursor = db.rawQuery(sql,selectionArgs);

  •  query(String table, String[] columns, String selection, String[] selectionArgs,String groupBy,String having, String orderBy);


查詢語法為:

String[] columns = {"name","phone"};

String selection = "name Like ? ";

String[] selectionArgs = {"%明%"};

Cursor cursor = db.query(TABLE_NAME , columns, selection, selectionArgs, null, null, null);

2017年7月9日 星期日

Android:使用BaseAdapter客製化ListView中的Item

在Android裡,ListView可以說是非常常用的一個元件,要使用ListView、Spinner、GridView這類清單元件,就必須使用Adapter,如果清單資料不需要很複雜時,可以使用ArrayAdapter和SimpleAdapter,但是如果清單元件如果有客製化需求時,就必須使用BaseAdapter


例如:此篇範例想要做出listView中的每一列的最前面都有圖片,並且偶數列給它顏色


BaseAdapter的特性就是可以重用View來節省資源,滑出去的View會被從新拿來使用,這樣就不必使用太多的資源了。
要使用BaseAdapter就必須讓一個類別來繼承它,並且實做四個方法,分別為:getCount()getItem()getItemId()getView()

  • getCount():會回傳View中的個數。

  • getItem(int postition):會回傳position所對應的資料。

  • getItemId(int position):會回傳position所對影的id值,可供辨識。

  • getView(int position , View convertView , ViewGroup parent):當欲展示一個項目給使用者時,會自動呼叫此方法,會把實體化過的view傳回去給listview。


程式碼:

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.example.user.baseadapter.MainActivity">

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/listview"></ListView>
    
</RelativeLayout>

list_row.xml:listview中每一列要顯示的元件的布局檔(一張圖片和文字)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <ImageView
        android:layout_width="10dp"
        android:layout_height="20dp"
        android:id="@+id/image"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textview"
        android:paddingLeft="30dp"/>

</LinearLayout>

Encapsulation.java:

public class ListName {
    private String name;

    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
}

BaseAdapter.java:

public class BaseAdapter extends android.widget.BaseAdapter{

        private ArrayList<ListName> listData;
        private LayoutInflater layoutInflater;
        public BaseAdapter(Context context, ArrayList<ListName> listData) {
            this.listData = listData;
            layoutInflater = LayoutInflater.from(context);  //獲得LayoutInflater
        }
        @Override
        public int getCount() {
            return listData.size();
        }
        @Override
        public Object getItem(int position) {
            return listData.get(position);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder holder;

            if (convertView == null) {  //判斷是否被使用過,如果沒有就初始化它
                convertView = layoutInflater.inflate(R.layout.list_row, null); //LayoutInflater.inflate來載入xml文件布局檔
                holder = new ViewHolder();  //實體化holder
                holder.image = (ImageView)convertView.findViewById(R.id.image);
                holder.textview = (TextView) convertView.findViewById(R.id.textview);

                convertView.setTag(holder);  //把convertView塞入holder裡,下次convertView不是空的條件下 就可以直接把holder拿出來用
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            ListName encapsulation = listData.get(position);

            holder.image.setBackgroundColor(Color.parseColor("#f6565a"));  //給image設定顏色
//            convertView.setBackgroundColor(Color.WHITE);

            if (position%2==0){   //判斷是不是偶數列,是的話就把那列設定顏色
                convertView.setBackgroundColor(Color.parseColor("#0ce8e2"));
            }

            holder.textview.setText(encapsulation.getName());
            return convertView;
        }
        static class ViewHolder {  //用來裝在listview裡每一列要顯示的元件
            TextView textview;
            ImageView image;

        }

}

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private ListView listview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String[] s={"Jack","Mary","William","Micheal","Tom","Watson","Alice","Anna","Eve","Jane","Amanda","Laura","Diana","Lydia","Ivy","Vanessa","Ashley","Jennifer","Stephanie","Bella","Catherine","Eileen"};

        listview = (ListView)findViewById(R.id.listview);
        ArrayList<ListName> list = new ArrayList<>();

        for(int i=0;i<s.length;i++){
            ListName encapsulation = new ListName();
            encapsulation.setName(s[i]);
            list.add(encapsulation);
        }
        listview.setAdapter(new BaseAdapter(this,list));


    }
}

2017年7月6日 星期四

Android:使用Socket的簡單聊天室範例

之前有在Java裡寫了Server-Client介紹,這篇就來使用Android來寫一個簡單的對話App,使用Socket和ServerSocket來連結Server和Client端,並且透過I/O傳遞資料

Client端:

 Server端:





Client端:
activity_main.xml:

<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="com.example.and_sv.MainActivity" >

    <EditText
        android:id="@+id/edtname"
        android:layout_width="100dp"
        android:layout_height="wrap_content" >
        
    </EditText>

    <EditText
        android:id="@+id/edttext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/edtname"
        android:layout_marginLeft="20dp"
        android:layout_toRightOf="@+id/edtname"
        android:ems="10" >
    </EditText>

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@+id/edttext"
        android:layout_below="@+id/edttext"
        android:text="送出" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/edtname"
        android:layout_below="@+id/button1" />

</RelativeLayout>

MainActivity.java:

public class MainActivity extends Activity {
 private EditText edtname,edttext;
 private TextView textview1;
 private Button button1;
 String tmp;                // 暫存文字訊息
    Socket clientSocket;
    
    public static Handler mHandler = new Handler();
    
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  edtname = (EditText)findViewById(R.id.edtname);
  edttext = (EditText)findViewById(R.id.edttext);
  button1 = (Button)findViewById(R.id.button1);
  textview1 = (TextView)findViewById(R.id.textView1);
  button1.setOnClickListener(btnlistener);
  
        Thread t = new Thread(readData);
         // 啟動執行緒
         t.start();

 }
 private OnClickListener btnlistener = new Button.OnClickListener(){

  @Override
  public void onClick(View v) {
   // TODO Auto-generated method stub
   if(clientSocket.isConnected()){
     
                BufferedWriter bw;

                try {
                    // 取得網路輸出串流
                    bw = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream()));

                    // 寫入訊息
                    bw.write(edtname.getText()+":"+edttext.getText()+"\n");

                    // 立即發送
                    bw.flush();
                } catch (IOException e) {

                }
                // 將文字方塊清空
                edttext.setText("");
            }    
  }
  
 };
  
     // 顯示更新訊息
     private Runnable updateText = new Runnable() {
         public void run() {
             // 加入新訊息並換行
             textview1.append(tmp + "\n");
         }
     };
  
     // 取得網路資料
     private Runnable readData = new Runnable() {
         public void run() {
             // server端的IP
             InetAddress serverIp;
             try {
                 // 以內定(本機電腦端)IP為Server端
                 serverIp = InetAddress.getByName("10.0.2.2");
                 int serverPort = 5050;
                 clientSocket = new Socket(serverIp, serverPort);
  
                 // 取得網路輸入串流
                 BufferedReader br = new BufferedReader(new InputStreamReader(
                         clientSocket.getInputStream()));
  
                 // 當連線後
                 while (clientSocket.isConnected()) {
                     // 取得網路訊息
                     tmp = br.readLine();
  
                     // 如果不是空訊息則
                     if(tmp!=null)
                         // 顯示新的訊息
                         mHandler.post(updateText);
                 }
  
             } catch (IOException e) {
  
             }
         }
     };
  
 }

Server端:

public class Server {
 
 private static int serverport = 5050;
    private static ServerSocket serverSocket;
    private static int count=0;
 
    // 用串列來儲存每個client
    private static ArrayList players=new ArrayList();
 
    // 程式進入點
    public static void main(String[] args) {
     
        try {
            serverSocket = new ServerSocket(serverport);
            System.out.println("Server is start.");
            // 顯示等待客戶端連接
            System.out.println("Waiting for clinet connect");
            // 當Server運作中時
            while (!serverSocket.isClosed()) {
                
                // 呼叫等待接受客戶端連接
                waitNewPlayer();
            }
 
        } catch (IOException e) {
            System.out.println("Server Socket ERROR");
        }
 
    }
 
    // 等待接受客戶端連接
    public static void waitNewPlayer() {
        try {
            Socket socket = serverSocket.accept();
            ++count;
            System.out.println("現在使用者個數:"+count);
 
            // 呼叫創造新的使用者
            createNewPlayer(socket);
            
        } catch (IOException e) {
 
        }
 
    }
 
    // 創造新的使用者
    public static void createNewPlayer(final Socket socket) throws IOException {
     
     
        // 以新的執行緒來執行
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 增加新的使用者
                    players.add(socket);
                    // 取得網路串流 
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(socket.getInputStream()));
                    // 當Socket已連接時連續執行
                    while (socket.isConnected()) {
                        // 取得網路串流的訊息
                        String msg= br.readLine();
                        
                        if(msg==null){
                         System.out.println("Client Disconnected!");
                         break;
                        }
 
                        //輸出訊息 
                        System.out.println(msg);
                        
                        // 廣播訊息給其它的客戶端
                        castMsg(msg);

                    }
                    
 
                } catch (IOException e) {
                 e.getStackTrace();
                }
                finally{
                 // 移除客戶端
              players.remove(socket);
              --count;
              System.out.println("現在使用者個數:"+count);
                }
             
            }
        });
 
        // 啟動執行緒
        t.start();
    }
 
    // 廣播訊息給其它的客戶端
    public static void castMsg(String Msg){
        // 創造socket陣列
        Socket[] ps=new Socket[players.size()]; 
 
        // 將players轉換成陣列存入ps
        players.toArray(ps);
 
        // 走訪ps中的每一個元素
        for (Socket socket : ps ) {
            try {
                // 創造網路輸出串流
                BufferedWriter bw;
                bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream()));
 
                // 寫入訊息到串流
                bw.write(Msg+"\n");
 
                // 立即發送
                bw.flush();
            } catch (IOException e) {
 
            }
        }
    }

}

PS:因為要持續的更新Textview所以必須使用new Handler().post(runnable)
在Android裡只有(Main Thread或UI Thread)主線程負責UI的操作或事件,子線程不可以插手有關UI的事,但是可以透過子線程去處理複雜的事情,再透過Handler去通知主線程更新UI

2017年7月5日 星期三

Java:Socket連線Server-Client

在Client-Server架構裡,不同的伺服器程式會使用不同的埠號,同時在伺服器裡執行,當Client端發出請求後,Server端的程式會建立一個Socket物件,並透過這個物件與Client端連線,以便進行資料的傳遞,Socket可以說是程式與網路之間的一種介面,要把資訊傳送到網路,就必須透過Socket的幫忙。

Java裡提供了ServerSocket與Socket類別,可將Client端的電腦連上伺服器,並可從伺服器傳遞資料給Client端。

Server端

public class Server{

  
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  try {
   ServerSocket svs = new ServerSocket(5050);  //建立ServerSocket物件,並設定埠號5050
   System.out.println("waiting for client");   
   Socket sk = svs.accept();                   //Client提出請求,accept()會傳回一個Socket物件,並讓sk指向它
   System.out.println("Clinet connected");
   while(sk.isConnected()){                    //當Socket持續在連接時,就做下面的事
    BufferedReader br = new BufferedReader(new InputStreamReader(sk.getInputStream()));
    String msg=br.readLine();
    if(msg==null){                      //當Client disconnect時,readline會傳回null
     break;
    }
    System.out.println(msg);
    
   }
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }

}

Client端

public class Client {


 public static void main(String[] args) {
  // TODO Auto-generated method stub

  BufferedWriter bw;
  try {
   Socket sk = new Socket("127.0.0.1",5050);    //建立Socket物件,並設定ip(127.0.0.1是自己的主機)和埠號
   System.out.println("已連線Server");
   bw = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));   //利用sk來取得輸出串流
   while(sk.isConnected()){
    BufferedReader  br=new BufferedReader(new InputStreamReader(System.in));  //透過輸入串流來取得Client的輸入
    System.out.print("client:");            
    bw.write("Client:"+br.readLine()+"\n");   //寫入輸出串流
    bw.flush();                               //立即傳送
   }
   
  }catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

}

PS:因為Server是在自己電腦執行,所以IP就是127.0.0.1
埠號使用者可以設定的只有1024~65535,0~1023為系統所保留

Server端











Client端

2017年7月2日 星期日

Java:I/O輸入與輸出、緩衝區(Buffer)

在Java裡,是以Stream串流的方式處理輸出與輸入,串流裡的資料是由字元(characters)與位元(bits)所組成,可以經由Stream從資料來源讀寫檔案,也可以將資料以字元或位元的型式寫到檔案裡。

串流又可分為輸出串流與輸入串流,我們可以透過InputStream、OutputStream、ReaderWriter類別來處理串流的輸入與輸出。


  • 處理純文字檔使用:Reader與Writer類別

  • 處理純文字檔二進位檔使用:InputStream與OutputStream類別

下面範例使用InputStream與OutputStream來處理圖片檔案:


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class JavaIO {

 public static void main(String[] args) throws IOException {
  // TODO Auto-generated method stub
  FileInputStream fi = new FileInputStream("D:\\java\\DSC00653.jpg");
  FileOutputStream fo = new FileOutputStream("E:\\DSC.jpg");
  System.out.println("File Size = " + fi.available());  //透過fi物件取得檔案大小,並列印出來
  byte data[] = new byte[fi.available()];       //建立byte型態的陣列,其大小剛好可以容納整個圖片檔案
  fi.read(data);
  fo.write(data);
  System.out.println("file copied and renamed");
  fi.close();
  fo.close();
 }

}

緩衝區(Buffer):
上面的方法沒有使用緩衝區,比起有使用緩衝區的處理方式而言,沒有使用緩衝區的程式比較沒有效率,因為不需要一直存取磁碟,這在處理比較複雜的文字檔的時候,可以感覺出來效率的差別。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class JavaIO {

 public static void main(String[] args) throws IOException {
  // TODO Auto-generated method stub
  FileInputStream fi = new FileInputStream("D:\\java\\DSC00653.jpg");
  BufferedInputStream bi = new BufferedInputStream(fi);
  FileOutputStream fo = new FileOutputStream("E:\\DSC.jpg");
  BufferedOutputStream bo = new BufferedOutputStream(fo);
  System.out.println("File Size = " + fi.available());
  byte data[] = new byte[fi.available()];
  bi.read(data);
  bo.write(data);
  System.out.println("file copied and renamed");
  fi.close();
  fo.close();
 }

}