2012年9月29日 星期六

[Android] 使用 TextSwitcher 切換ListView的內容 (How to use TextSwitcher change ListView content)



    
    透過TextSwitcher 的切換來改變ListView 的內容, 並且在切換的同時加入Animation 動畫. 然後使用Typeface將TextView的字體改變, 意即使用custom Font. 而為了要使用custom Font需將字體檔 wt021.ttf 事先匯入 assert/font folder, 到時候再透過 Typeface.createFromAsset(getAssets(), "fonts/wt021.ttf") 取用, 特別注意要加上副檔名 .ttf



首先來看 MainActivity的部分,



package com.example.textswitcher;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.ListView;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;

public class MainActivity extends Activity
{

    TextSwitcher TSwitcher;
    static int textIndex = 0;
    
    String[] TSwitcherContent;
    ListView LView;
    TextArrayAdapter LAdapter;
    
    Typeface font;
    
 
 @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        font = Typeface.createFromAsset(getAssets(), "fonts/wt021.ttf");
        //從 assert folder 中取用我們匯入的自訂字體 wt021.ttf  
        
        iniTexTSwitcher();
        iniListView();
    }
    
 private void iniTexTSwitcher()
    {
  // TODO Auto-generated method stub
     
  TSwitcher = (TextSwitcher) findViewById(R.id.textSwitcher);
  
  //透過 setFactory method 產生兩個 Views給 TextSwitcher切換
     TSwitcher.setFactory(new ViewFactory(){
      
      //實作makeView() method 產生 View供TextSwitcher作切換 
      public View makeView()
      {       
       TextView tv =new TextView(MainActivity.this);
       tv.setTypeface(font); // 使用自訂的字體
       tv.setTextSize(40);
       tv.setTextColor(Color.GREEN);       
       tv.setGravity(Gravity.CENTER);           
       return tv;
      }
     });
     
     TSwitcherContent = getResources().getStringArray(R.array.textSwitcherContent);
     //將事先定義在 /res/value 的string array 取出使用
     
     TSwitcher.setText(TSwitcherContent[textIndex]);      
     
     //替 TextSwitcher註冊 onClickListener
     TSwitcher.setOnClickListener(new View.OnClickListener()
     {
      public void onClick(View v)
      {       
       textIndex++;
       
       if (textIndex >= TSwitcherContent.length) textIndex = 0;
         
       TSwitcher.setText(TSwitcherContent[textIndex]);       
       
       LAdapter.notifyDataSetChanged();
       //告知Adapter的data 已經改變了, 所以我們手動的呼叫 notifyDataSetChanged() method
       
       LView.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), android.R.anim.fade_in));
       //當TextSwitcher切換時, 讓ListView有淡入的效果
      }
     });      
      
     TSwitcher.setInAnimation(AnimationUtils.loadAnimation(getApplicationContext(), android.R.anim.slide_in_left));       
     // TextSwitcher切換時也使用動畫效果
 }

 private void iniListView()
    {
  // TODO Auto-generated method stub  
  
  LView = (ListView) findViewById(R.id.listView);
  
  LAdapter = new TextArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1, TSwitcherContent, font);
  //宣告使用custom TextArrayAdapter that extends ArrayAdapter
  //android.R.layout.simple_list_item_1, 為android system resource layout 只能顯示single line, 所以本身即為一個TextView  
  
  LView.setAdapter(LAdapter);  
 }

 
 @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}


接著來談一下TextSwitcher吧, TextSwitcher的父類別為 ViewSwitcher, 所以具有和ViewSwitcher共同的性質, ViewSwitcher 透過 setFactory(new ViewFactory()) 產生兩個child views給ViewSwitcher作切換, ViewSwitcher 只能夠有兩個 child views, 並且一次只能顯示一個, 另外 ViewSwitcher 有兩個子類別, 一為TextSwitcher 另一個為 ImageSwitcher, 差別在於TextSwitcher 所包含的 child views 只能為 TextView type的 view, ImageSwitcher 只能擁有 ImageView type的 child view



處理完畢 MainActivity.java 的內容之後我們來看 ListView, 首先ListView 和 data 之間必須有Adapter作為配接, 在這邊我們使用custom Adapter that extends ArrayAdapter, 我已經將這個custom adapter 取名為 TextArrayAdapter, 並且獨立一個.java 如下


package com.example.textswitcher;

import android.app.Activity;
import android.content.Context;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class TextArrayAdapter extends ArrayAdapter
{

 Activity mainActivity;
 Typeface itemFont;
 
 public TextArrayAdapter(Context context, int textViewResourceId, String[] objects, Typeface font)
 {
  super(context, textViewResourceId, objects);
  // TODO Auto-generated constructor stub
  
  mainActivity = (Activity) context;
  //特別取得 MainActivity
  
  itemFont = font;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent)
 {
  // TODO Auto-generated method stub
  
  View rowview = convertView;
        if (rowview == null) {
                LayoutInflater inflater = mainActivity.getLayoutInflater();
                rowview = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
                //為了要把xml所描述的Layout轉變為View , 也就是我們要的rowview
                //所以必須使用LayoutInflater來轉化 , 而要取得LayoutInflater的Instance方式還包括
                //LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                //接著再使用inflate (int resource, ViewGroup root, boolean attachToRoot)
                //傳回我們要的rowview
        }
        
        TextView item = (TextView) rowview;
        
        item.setTypeface(itemFont);
        item.setGravity(Gravity.CENTER);
        
        //根據當前的 textIndex 來決定ListView該呈現的內容
        switch(MainActivity.textIndex)
        {
         case 0:
          item.setText("First");
         break;
         
         case 1:
          item.setText("Second");
         break;
         
         case 2:
          item.setText("Third");
         break;
         
         case 3:
          item.setText("Fouth");
         break;        
        }
  
  return rowview;
 } 

}



最後是簡單的 activity_main.xml


    
    

    

    






2012年9月27日 星期四

[雜談] Hello, I`m Back !



    

         看了上一篇文章的發文日期在四月份, 發覺荒廢了很久已經快半年沒發新文章了, 這段時間都在寫C 和忙別的雜事, 最近因為工作的關係又重新回到久違的行動平台了, 接到主管交付開發Android app, iOS app的任務, 按慣例要將開發 app學到的新心得整理放上來, 所以之後也會有對於開發iOS心得的相關文章, 讓自己以後可以快速的找查相關資料也方便檢視所學, 最後希望這些心得對app開發者們有幫助, 加快自己進入這個領域和縮短開發app的時間。




2012年4月10日 星期二

[TQC+ Android] 3-10 程式背景音樂 Use MediaPlayer






設計程式背景音樂 , 該音樂可在應用程式執行階段重複播放 , 當手機按下退出鍵或是程式失去焦點時 , 停止播放背景音樂 , 這題的內容在 [TQC+ Android] 3-3 MP3 播放器 Use MediaPlayer 都差不多講完了 , 沒甚麼特別的技巧 , 考試跳出這題的話算你賺到。




package COM.TQC.GDD03;

import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;


public class GDD03 extends Activity
{
  /** Called when the activity is first created. */
  
  MediaPlayer mPlayer;
  
 
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.main);    
     
     try
     {
      mPlayer = MediaPlayer.create(this, R.raw.light);
      mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
      
      mPlayer.setLooping(true);
      //重複播放
      
      //mPlayer.prepare();
      //特別使用註解的方式, 是為了提醒大家, 由於我們先前使用create method建立MediaPlayer
       //create method會自動的call prepare(), 所以我們再call prepare() method會發生 prepareAsync called in state 8的錯誤
         
  }catch (IllegalStateException e)
  {
  // TODO Auto-generated catch block
  e.printStackTrace();
  } 
  }
  
  @Override
  protected void onResume()
  {
 // TODO Auto-generated method stub   
   
   super.onResume();
   mPlayer.start();
  }

  @Override
  protected void onPause()
  {
 // TODO Auto-generated method stub   
   
   super.onPause();
   mPlayer.pause();
  }

  @Override
  protected void onDestroy()
  {
   // TODO Auto-generated method stub   
   super.onDestroy();   
   mPlayer.release();
  }  
}


這題連個Layout都不用設計 , AndroidManifest.xml 也不用特別額外宣告 , 所以都不附上 , 記得先把音樂檔放到 res/raw 資料夾底下就好。




P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....





[TQC+ Android] 3-9 畫布程式 Use Custom SurfaceView





設計一個全螢幕且背景為黑色的畫布程式 , 支援手指單點觸控 , 於手指按下螢幕時捕捉該觸控點的座標 , 以該觸控點座標為基準點繪製一個10*10的白色實心矩形 , 且每一次繪製時皆會清除前一次遺留的矩形 , 這題會使用自訂的SurfaceView , 並且實作處理手勢變化的 OnGestureListener 及實作一個Runnable 讓 non-UI Thread 幫我們處理Canvas的更新。


package COM.TQC.GDD03;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;


public class GDD03 extends Activity
{
  public static SView mSurfaceView01;
  public static SurfaceHolder mSurfaceHolder01;
  public GestureDetector detector;  
   

  /** Called when the activity is first created. */
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);  
    
    mSurfaceView01 = (SView) this.findViewById(R.id.surfaceView1);
    //從xml檔中取用我們的 CustomView
    
    detector = new GestureDetector(this,mSurfaceView01); 
    //GestureDetector 能幫我們監聽觸控螢幕上的手勢變化
    //它需要一個實作好的 GestureDetector.OnGestureListener interface
    //而這個例子中我們已經傳入 mSurfaceView01 , 因為它實作了 GestureDetector.OnGestureListener
   
  }  
  
  @Override
  public boolean onTouchEvent(MotionEvent event)
  {
 // TODO Auto-generated method stub
   
 //return super.onTouchEvent(event);
 //這是原本method 預設的回傳值 , 在這個例子使用 detector.onTouchEvent(event) 取代
    
 return detector.onTouchEvent(event);
 //GestureDetector 會根據傳入的MotionEvent 判斷手勢的變化 
 //另外它只會判斷Touch events 所以我們將Touch screen傳入的 events 
 //送到 GestureDetector class 的 onTouchEvent(event) , 讓它判斷是哪一種手勢
 //此外GestureDetector 不接受 trackball events  
 
  }
}


在這個class中先宣告Custom SurfaceView : mSurfaceView01 , mSurfaceView01 已經獨立出來存在另一個檔案 SView.java , 另外再使用 GestureDetector 替 mSurfaceView01 註冊監聽事件 , 讓 mSurfaceView01 可以根據手勢變化回應我們想要的動作。


當我們收到 Touch Screen 所傳入的Motion Events , 再將Events 傳入GestureDetector class 中的 onTouchEvent(event) method 去進一步的判斷是哪種手勢 , 再回應相對應的動作。



package COM.TQC.GDD03;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.GestureDetector.OnGestureListener;


public class SView extends SurfaceView implements SurfaceHolder.Callback , OnGestureListener , Runnable 
{ 
 SurfaceHolder mSurfaceHolder01;
 Canvas mCanvas;
 
 //我們已經在xml中定義了自訂的 SurfaceView , 所以系統會選用這個建構子去產生instance
 public SView(Context context, AttributeSet attrs)
 {
  // TODO Auto-generated constructor stub
  
  super(context,attrs);  
  
  mSurfaceHolder01 = this.getHolder();
  //獲取holder , 用來存取及控制 SurfaceView
  
  mSurfaceHolder01.addCallback(this);
  //監聽SurfaceHolder的事件
  
  setFocusable(true);
 } 

 @Override
 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
 {
  // TODO Auto-generated method stub  
 }

 @Override
 public void surfaceCreated(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub 
  
  new Thread(this).start();
  //當Surface 被建立時 , 產生一個Thread來處理畫布內容的改變
 }

 @Override
 public void surfaceDestroyed(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub
  
  ThreadFlag = false;
  //終止Thread 的運作  
 }
 
 @Override
 public void onDraw(Canvas canvas)
 {   
  // onDraw() method 已經有 SurfaceHolder.lockCanvas() 的效果了
  // 我們也不用額外的對canvas 執行unlock 的動作
  
  canvas.drawColor(Color.BLACK);
  //把畫布填充為黑色    
  Paint p = new Paint();
  p.setColor(Color.WHITE);
       
  if(DrawFlag) canvas.drawRect(x-5, y-5, x+5, y+5, p);
  // 畫一個矩形  
     
  DrawFlag = false;
  //關閉更新畫布開關 , 直到下一次的Touch 事件發生
 }

 float x , y;
 boolean ThreadFlag = true , DrawFlag = false;

 @Override
 public boolean onDown(MotionEvent arg0)
 {
  // TODO Auto-generated method stub
  
  x = arg0.getRawX();
  y = arg0.getRawY();
  //獲取觸控的座標
  
  DrawFlag = true;
  //開啟更新畫布的開關 
  
  return true;
 }
 
 @Override
 public void run()
 {
  // TODO Auto-generated method stub
  
  while(ThreadFlag)
  {
   if(DrawFlag) this.postInvalidate();
   //由於我們是在non-UI Thread中要求要更新畫布
   //所以使用 postInvalidate() method
  }
 }  

 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
   float velocityY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onLongPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
   float distanceY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onShowPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onSingleTapUp(MotionEvent e)
 {
  // TODO Auto-generated method stub  
  Log.d("Test", "onSingleTapUp");
  return true;
 }
}


至於我們自訂的SurfaceView 它是如何運作的? 我們會在Surface Created 的時候開始run一個non-UI Thread , 這個Thread 專門負責去呼叫 postInvalidate() method 來更新Canvas , 此外postInvalidate() method 會去執行 onDraw() method , 所以我們將畫布要呈現的內容都寫在onDraw() 中等著被執行 , 此外使用一個flag來控制畫布更新的時機 (是在觸控到螢幕時) , 此外當Surface Destroyed 的時候終止Thread 的運作。




接下來是 main.xml的部分 , 請注意我們是使用自訂的SurfaceView
    










P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....







2012年4月8日 星期日

[TQC+ Android] 3-8 判斷地標所屬區域 Use LocationManager




設計判斷地標所屬區域 , 設計一個判讀程式 , 判斷使用者手持手機時 , 該使用者是否在小巨蛋的區域內 , 一旦手機進入或是走出小巨蛋的區域 , 即變更TextView 的提示文字 , 以下是程式部分。



package COM.TQC.GDD03;


import android.app.Activity;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;


public class GDD03 extends Activity implements LocationListener
{
  public String strLocationProvider = "";
  public TextView mTextView01;
  LocationManager LManager;  
  double latitute , longtitute;
    
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    mTextView01 = (TextView)findViewById(R.id.myTextView1); 
    getLocationPrivider();
  }
  
  public void getLocationPrivider()
  {
    try
    {
      Criteria mCriteria01 = new Criteria();
      mCriteria01.setAccuracy(Criteria.ACCURACY_FINE);
      //定位精準標準 , Criteria.ACCURACY_FINE 為精確      
      mCriteria01.setAltitudeRequired(false);
      //是否需要海拔高度的資訊
      mCriteria01.setBearingRequired(false);
      //是否需要方向資訊
      mCriteria01.setCostAllowed(true);
      //是否同意用到付費的GPS Provider
      mCriteria01.setPowerRequirement(Criteria.POWER_LOW);
      //設定定位所需要的的電力等級
      
      LManager = (LocationManager) this.getSystemService(LOCATION_SERVICE);
      strLocationProvider = LManager.getBestProvider(mCriteria01, true);
      //依照我們上面所制定的要求 , 取得最佳的定位方式(定位衛星)
      //由於我是使用模擬器 , 所以回傳的定位提供者是Networks
      //由於我們想要的是一個GPS , 所以之後我們會使用 LocationManager.GPS_PROVIDER
        
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }  

  @Override
  protected void onResume()
  {
 // TODO Auto-generated method stub
 super.onResume();
 
 Location location = LManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
 //透過 getLastKnownLocation() method 可以得知上一次定位提供者所提供的Location資訊
 //所以程式在一開始執行時就可以判斷上一次的座標是否落在小巨蛋內
 
 if(location != null) getWhere(location);
 
 LManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 100, this);
 //註冊監聽事件 , 最小更新區間為5000 milisecond , 100 meter
 
  }
  
  @Override
  protected void onPause()
  {
 // TODO Auto-generated method stub
 super.onPause();
 LManager.removeUpdates(this);
 //移除更新座標的要求
  }
  
  @Override
  public void onLocationChanged(Location location)
  {
 // TODO Auto-generated method stub
 getWhere(location);
 //當偵測到座標改變時會取得座標
  }

  private void getWhere(Location location)
  {
 // TODO Auto-generated method stub
 latitute = location.getLatitude();
 longtitute = location.getLongitude();
 
 Log.d("Test", "getLatitude():"+latitute+" getLongitude()"+longtitute);
 
 //以下是判斷座標是否座落在小巨蛋內的方法
 
 if(latitute > 25.051578 || latitute < 25.051201 || longtitute>121.549666 || longtitute<121.549197)
    {
  //以這四個點作判斷剛好可以圍成一個長方形的區域 , 區域外的部分一定是小巨蛋的區域之外
  //但是這個長方形內仍有一小部分的區域仍不屬於小巨蛋 , 剩下這一小部分區域是一直角三角形
     mTextView01.setText("Outside Taipei Area");
    }
    else
    {
     //接下來我們可以使用向量外積的方式來判斷座標是否落在這直角三角形內 , CrossProducts 超過 0 代表落在直角三角形內也就是不在小巨蛋裡
     
     double ModelLineVectorX = 25.051216 - 25.051201 , ModelLineVectorY = 121.549666 - 121.549197;
     double UserLineVectorX = latitute - 25.051201 , UserLineVectorY = longtitute - 121.549197;
     
     double CrossProducts = UserLineVectorX * ModelLineVectorY - ModelLineVectorX * UserLineVectorY;
     
     if(CrossProducts <= 0) mTextView01.setText("Within Taipei Area");
     else mTextView01.setText("Outside Taipei Area"); 
    }
  }

  @Override
  public void onProviderDisabled(String provider)
  {
 // TODO Auto-generated method stub 
  }

  @Override
  public void onProviderEnabled(String provider)
  {
 // TODO Auto-generated method stub 
  }

  @Override
  public void onStatusChanged(String provider, int status, Bundle extras)
  {
 // TODO Auto-generated method stub 
  }    
}



再來是 AndroidManifest.xml , 因為我們要存取使用者的座標還要使用網路 , 所以要記得去註冊所需要的 use-permission。



    
    
    
    
    
        
            
                
                
            
        

    
    

 



使用模擬器來發送模擬的座標







P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....




Google Analytics