2014年2月13日 星期四

Android 開發 (二十八) onInterceptTouchEvent 和 onTouchEvent 的處理

為何要瞭解event的處理

有些時候會在viewgroup裡放其他View,然後會希望某些view的touch event不動作,這時候就必須設法去處理onInterceptTouchEvent  和  onTouchEvent。
例如在viewpager裡面放 imageview,當click在imageview時不希望viewpager有動作,又或者listview 裡放button,但是只希望接收到listview click event。


甚麼是onInterceptTouchEvent  和  onTouchEvent

onInterceptTouchEvent就是用來攔截事件,而onTouchEvent是用來處理事件。
當事件被InterceptEvent攔截之後就不會再往下傳遞給childLayout,
然而當事件被Touchevent處理過後就不會再向ParentLayout傳遞。


當所有的event回傳值都是false時
可以看到debug log 從
 listview OninterceptEvent -> TextView OninterceptEvent
->TextView OnTouchEvent -> listview OnTouchEvent
假設 textview 的 onTouchevent 回傳值為 true
Debug log會變成
 listview OninterceptEvent -> TextView OninterceptEvent ->TextView OnTouchEvent
由於事件被textview處理完成 ,所以listview就不需再處理。
假設 listview 的 onInterceptEvent 回傳值為 true
Debug log會變成
 listview OninterceptEven ->ListView OnTouchEvent
由於事件被Listview攔截 ,所以textview就不會接收到event。

結論

利用onInterceptTouchEvent  和  onTouchEvent這兩個event,
就可以輕鬆客制化自己想要的行為。

2014年2月11日 星期二

Android 開發 (二十七) tabhost 加上viewpager 使用

Tabhost以及ViewPager介紹

如下圖,在設計app時,常常會使用到這種分頁方式,
例如我寫了一個email app, tab a為  hotmail  , tab b 為 gmail  ....以此類推,
tabhost就是上面的tab,而viewPager讓我們能夠利用滑動切換頁面
兩個加在一起的話,有較好的使用者經驗。


要如何使用

首先必須先創建 main.xml
在xml可以看到,layout裡面同時擁有 tabhost 以及viewpager

<TabHost android:layout_height="fill_parent" android:layout_width="fill_parent" android:id="@+id/tabhost" xmlns:android="http://schemas.android.com/apk/res/android">


<LinearLayout android:layout_height="fill_parent" android:layout_width="fill_parent" android:orientation="vertical">

<TabWidget android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@android:id/tabs" android:layout_marginRight="3dp" android:layout_marginLeft="3dp"> </TabWidget>

<android.support.v4.view.ViewPager android:layout_height="fill_parent" android:layout_width="fill_parent" android:id="@+id/viewpager"/>

<FrameLayout android:layout_height="fill_parent" android:layout_width="fill_parent" android:id="@android:id/tabcontent" android:visibility="gone"> </FrameLayout>

</LinearLayout>

</TabHost>

接著在 tabFragment內


	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.login_tab_layout, container,false);
		mPager  = (ViewPager) view.findViewById(R.id.viewpager);
		mTabHost = (TabHost)  view.findViewById(R.id.tabhost);
		mTabHost.setup();	
		mTabHost.setOnTabChangedListener(new OnTabChangeListener() {
            @Override
            public void onTabChanged(String tabId) {
            	mTabHost.setOnTabChangedListener(new OnTabChangeListener() {
                        @Override
                        public void onTabChanged(String tabId) {                           	
                        		mPager.setCurrentItem(mPager.getAdapter().getItemPosition(fragmentMap.get(tabId)));
                        }
                    });
                
            }
        });
		
        mPager.setOnPageChangeListener(new OnPageChangeListener() {
            
        	@Override
            public void onPageSelected(int position) {            	
            		mTabHost.setCurrentTab(position);
            }
            
            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {}
            
            @Override
            public void onPageScrollStateChanged(int arg0) {}
        });
		return view;
	}		

在點擊tabhost時同時切換viewpager
在切換viewpager時同時切換tabhost

接著要添加fragment在viewpager內,以及添加tab在Tabhost內

public void addFragmentTab(int titleResId, int tabViewResId, Fragment fragment , String tag){
		list.add(fragment);
		fragmentMap.put(tag, fragment);
		TextView tabView = (TextView) LayoutInflater.from(getActivity()).inflate(tabViewResId, null);
		tabView.setText(titleResId);

		TabSpec tabSpec = mTabHost.newTabSpec(tag).setIndicator(tabView);
	    tabSpec.setContent(new TabFactory(getActivity()));
	    mTabHost.addTab(tabSpec);
	   
	}


接著就完成了
搭配 Android 開發 (二十五) how to change bottom color of tabhost 
就可以完成客製化tab

附上 git 的project url

Android 開發 (二十六) handle hyperlink in textView(二)

handle hyperlink in textView(一) 中有提到如何使 textview 能夠處理url 使之能夠被點擊。
但是如果僅使用那邊的寫法,當將這個textview放到 listview內會造成 itemclick無法被點擊的問題。

為何會造成這個問題?

由於設定 setmovementmethod會同時 
  • setFocusable(true);
  • setClickable(true);
  • setLongClickable(true);
簡單的說就是 Event 都被 Textview處理掉了,所以listview拿不到itemclick event。
所以我們必須客製化touch event,只有在url被點擊的時候,才使用Textview去處理,其他時候就將event 往下傳遞。


首先要稍微提一下 onTouchEvent的 override
return true  代表這個事件在這邊被處理,所以不會再往下傳遞。
return false 代表事件並未被處理,會繼續向下傳遞。




首先先客製化 LinkMovementMethod 只有在url 被點擊時才處理 (return true)

 public static class LocalLinkMovementMethod extends LinkMovementMethod{
     static LocalLinkMovementMethod sInstance;


     public static LocalLinkMovementMethod getInstance() {
         if (sInstance == null)
             sInstance = new LocalLinkMovementMethod();

         return sInstance;
     }

     @Override
     public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
         int action = event.getAction();

         if (action == MotionEvent.ACTION_UP ||
                 action == MotionEvent.ACTION_DOWN) {
             int x = (int) event.getX();
             int y = (int) event.getY();

             x -= widget.getTotalPaddingLeft();
             y -= widget.getTotalPaddingTop();

             x += widget.getScrollX();
             y += widget.getScrollY();

             Layout layout = widget.getLayout();
             int line = layout.getLineForVertical(y);
             int off = layout.getOffsetForHorizontal(line, x);

             ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

             if (link.length != 0) {
                 if (action == MotionEvent.ACTION_UP) {
                     link[0].onClick(widget);
                 } else if (action == MotionEvent.ACTION_DOWN) {
                     Selection.setSelection(buffer,
                             buffer.getSpanStart(link[0]),
                             buffer.getSpanEnd(link[0]));
                 }

                 if (widget instanceof LinkEnabledTextView){
                     ((LinkEnabledTextView) widget).linkHit = true;
                 }
                 return true;
             } else {
                 Selection.removeSelection(buffer);
                 Touch.onTouchEvent(widget, buffer, event);
                 return false;
             }
         }
         return Touch.onTouchEvent(widget, buffer, event);
     }
 }


接著在客製化的 textView 裡面 override touchevent 和 focus


 @Override
 public boolean hasFocusable() {
  // TODO Auto-generated method stub
  return false;
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event) {
     linkHit = false;
     boolean res = super.onTouchEvent(event);

     if (dontConsumeNonUrlClicks)
         return linkHit;
     return res;

 }


經過這樣的修改,listview的onitemclick event就不會再被textview吃掉了。

2014年2月10日 星期一

Android 開發 (二十五) how to change bottom color of tabhost

需求

如何更改tabhost 底下那條線的顏色,我花了不少時間尋找可用的方法,可惜一直找不到較好的方法,最後的方法是自己製作圖然後更換背景圖,效果比想像的還要好。
如下圖



實現方法

首先先實做兩個背景圖

line_label_1_pressed.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:top="-6dp" android:left="-6dp" android:right="-6dp">
        <shape>
            <size android:height="50dp"/>
            <solid android:color="@android:color/transparent"/>
            <stroke android:color="@color/myColor" android:width="6dp"/>
        </shape>
    </item>
</layer-list>

line_label_1.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item>
        <shape>
            <solid android:color="@android:color/transparent" />
        </shape>
    </item>

</layer-list>



接著使用selector
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/line_label_1_pressed" android:state_selected="true"/>
    <item android:drawable="@drawable/line_label_1"/>
</selector>


接著只需要在 view 裡面使用這個背景即可, 範例如下

android:background="@drawable/tab_line_selector_bottom"

當被selected的時候就會顯示, line_label_1_pressed 其他時候就會使用line_label_1的xml。

Android 開發 (二十四) handle hyperlink in textView(一)

為何需要客製化  textView 來處理hyperlink

最近在寫案子的時候遇到一個需求, 希望 textView 能夠偵測 http:// 並且判斷該url為何,並根據不同的url做不同的動作,假設只需要判斷 hyperlink  使用 textview 內建的android:autolink就可以完成,但是內建的textView並不能偵測click的動作,所有的hyperlink都會自動導向browser ,這並符合我們的需求,經過了一段時間的尋找,總算找到了解法,在這邊跟大家分享。


使用的原理

由於 textview 可以使用 SpannableString 然而 SpannableString 有個method 叫 setSpan ,而setSpan的
第一個參數可以傳各式各樣的Span,其中有一個叫做 ClickableSpan ,使用這個span讓我們可以接收到click event, 但是要使hyperlink可以點擊還必須多做一道手續,setMoveMentMethod 在設定完成之後,我們的功能就算大功告成了。

程式碼範例



 
 public void seTextLinkClickListener(TextLinkClickListener listener)
 {
  mListener = listener;
     MovementMethod m = getMovementMethod();
     if ((m == null) || !(m instanceof LinkMovementMethod)) {
         if (getLinksClickable()) {
          setMovementMethod(LinkMovementMethod.getInstance());
         }
     }
 }
        public void GenerateLink(String text)
 {
  SpannableString linkableText = new SpannableString(text);

     gatherLinks(listOfLinks, linkableText, hyperLinksPattern);

     for(int i = 0; i< listOfLinks.size(); i++)
     {
         Hyperlink linkSpec = listOfLinks.get(i);
         linkableText.setSpan(linkSpec.span, linkSpec.start, linkSpec.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
     }

     setText(linkableText);
 }

 private final void gatherLinks(ArrayList<Hyperlink> links, Spannable s,
   Pattern pattern) {
  
  Matcher m = pattern.matcher(s);
  
  while (m.find()) {
   int start = m.start();
   int end = m.end();

   Hyperlink spec = new Hyperlink();

   spec.textSpan = s.subSequence(start, end);
   spec.span = new InternalURLSpan(spec.textSpan.toString());
   spec.start = start;
   spec.end = end;

   links.add(spec);
  }
 }
 public class InternalURLSpan extends ClickableSpan
 {
     private String clickedSpan;

     public InternalURLSpan (String clickedString)
     {
         clickedSpan = clickedString;
     }

     @Override
     public void onClick(View textView)
     {
         mListener.onTextLinkClick(textView, clickedSpan);
     }
 }
 
 class Hyperlink
 {
     CharSequence textSpan;
     InternalURLSpan span;
     int start;
     int end;
 }


接著只需要再mainActivity裡面使用這個檔案就可以了

附上 sample code
網路上也有相關的Git Project

2014年2月5日 星期三

Android 開發 (二十三) google console key 取得

在開發時常常需要用到google api,
在使用google api就必須前往 Google Console取得permission
首先將要使用的API turn on


接著Create Key  例如我是寫android的  就create android key

接著必須填入project 的 name 以及SHA1 Fingerprint
例如我創造一個project  mytubesample 我的 project name
就是com.example.mytubesample


該如何找到 SHA1 key  這是一個問題,
如果是使用eclipse就可使用下面的方式
Window -> Preferences -> Build -> SHA1 fingerprint

SHA1 Fingerprint就是我們需要的SHA1 Fingerprint
在創建完成之後


我們會看到API key  只需要將該key填入api中即可使用API

Android 開發 (二十二) 使用 Youtube SDK

如何使用youtube SDK
如同所有Google的sdk ,必須申請 developer key
在取得key之後,使用下方的sample code 就可簡單的使用 youtube sdk


public class MainActivity extends YouTubeFailureRecoveryActivity implements
  PlaybackEventListener {
 YouTubePlayer mYouTubePlayer;
 YouTubePlayerView youTubeView;
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

     youTubeView = (YouTubePlayerView) findViewById(R.id.youtube_view);
  youTubeView.initialize(DeveloperKey.DEVELOPER_KEY, this);
 }

 @Override
 public void onInitializationSuccess(YouTubePlayer.Provider provider,
   YouTubePlayer player, boolean wasRestored) {
  if (!wasRestored) {
   player.cueVideo("wKJ9KzGQq0w");
  }
  mYouTubePlayer = player;
  player.setPlayerStyle(PlayerStyle.MINIMAL);
  player.setPlaybackEventListener(this);
 }

 @Override
 public void onBackPressed() {
  // TODO Auto-generated method stub
  mYouTubePlayer.setFullscreen(false);
 }
 @Override
 protected YouTubePlayer.Provider getYouTubePlayerProvider() {
  return (YouTubePlayerView) findViewById(R.id.youtube_view);
 }

 @Override
 public void onBuffering(boolean arg0) {

  mYouTubePlayer.setFullscreen(true);
 }

 @Override
 public void onPaused() {
  // TODO Auto-generated method stub

 }

 @Override
 public void onPlaying() {
  // TODO Auto-generated method stub

 }

 @Override
 public void onSeekTo(int arg0) {
  // TODO Auto-generated method stub

 }

 @Override
 public void onStopped() {
  // TODO Auto-generated method stub

 }

}

其中 YouTubeFailureRecoveryActivity 為 youtube sample code 所附加的檔案
youtube 的 sample code 可以在此下載

在這裡要特別提及的重點是,當使用fullscreen全螢幕撥放時,
很多人都會發現無法恢復成portrait mode
要恢復成portrait mode 其實很簡單,
注意我在 onbackpress做的事情,
當backpress時我將fullscreen mode設為false 這樣就能恢復成portrait mode了
附上sample code 供參考