2016年12月4日 星期日
要怎麼查看 iOS Simulator 上 App 的 NSUserDefault?
~Library/Developer/CoreSimulator/Devices/{Simulator Id}/data/Containers/Data/Application/{Application Id}/Library/Preferences/{App name}.plist
2016年11月16日 星期三
[Git] 將多個 commits 整併 (squash) 在一起
Git: Squash your latests commits into one
Ref: https://ariejan.net/2011/07/05/git-squash-your-latests-commits-into-one/2016年11月13日 星期日
Share plain text to other app
String shareBody = "Here is the share content body"; Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Subject Here"); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody); startActivity(Intent.createChooser(sharingIntent, "Share Prompt Title"));
* Share Dialog |
2016年10月17日 星期一
Android ReactiveX 新手入門
Getting Started With ReactiveX on Android
前言
在開發 Android 時,常常為了處理網路連線、後端 API 呼叫與 UI 的更新,而寫出一堆 callback,甚至是 nested callbacks,這些程式碼不僅難看,也容易寫錯。使用 ReativeX 可以讓程式碼更清楚明瞭,更可以節省開發的時間。設定 RxAndroid
compile 'io.reactivex:rxandroid:1.2.1' // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. compile 'io.reactivex:rxjava:1.2.1'PS: 可以到 GitHub 上查看目前最新的版本
基礎:Observables 與 Observers
當在開發 ReactiveX 時,我們會不斷的使用 observables 和 observers,因為 ReativeX 正正是套用 Observer Design Pattern 的設計,Observable 是可被觀察的對象,也就是真正在做事的人,Observer 是觀察者,聆聽與接收事件發生的人。建立一個簡單只傳遞出 "Hello" 的 Observable:
Observable<string> myObservable = Observable.just("Hello"); // Emits "Hello"
建立一個 Observer:
Observer<string> myObserver = new Observer<string>() { @Override public void onCompleted() { // Called when the observable has no more data to emit } @Override public void onError(Throwable e) { // Called when the observable encounters an error } @Override public void onNext(String s) { // Called each time the observable emits data Log.d("OBSERVER1", s); } };在沒有 Observer 觀察者的時候,Observable 是不會傳遞出資料的,想要 Observable 傳遞出資料,就必須要先向其訂閱 (Subscribe):
Subscription mySubscription = myObservable.subscribe(myObserver);此時 myObserver 就會接收到 "Hello" 字串。
除了 observer 可以 subscribe 以外,有時我們不需要 onCompleted 和 onError 時,也可以用 Action1 的介面來 subscribe:
Subscription mySubscription = myObservable .subscribe(new Action1<string>() { @Override public void call(String s) { Log.d("ACTION1", s); } });
使用 Operators
from
ReactiveX 提供了許多對 observables 操作的運算方法,先來介紹 Observable.from 的用法,from 可以讓你提供一組數據資料,使每個資料都成爲一個 Observable,而 Observer 接受到的資料就會是一個接著一個來,而不是一次收到整組數據。Observable<Integer> myArrayObservable = Observable.from(new Integer[]{1, 2, 3, 4, 5, 6}); // Emits each item of the array, one at a time myArrayObservable.subscribe(new Action1<Integer>() { @Override public void call(Integer i) { Log.d("My Action", String.valueOf(i)); // Prints the number received } });
map
透過 map 可以將原本 Observable 的結果,做想要的運算,再回傳另一個新的 Observable。myArrayObservable .map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { return integer * integer; } }) .subscribe(new Action1<Integer>() { @Override public void call(Integer i) { Log.d("ACTION1", i.toString()); } });
skip
skip 可以用來忽略前面幾個的回傳結果,例如 skip(2) 回傳的結果就會是 3, 4, 5, 6myArrayObservable .skip(2) .subscribe(new Action1<Integer>() { @Override public void call(Integer i) { Log.d("ACTION1", i.toString()); } });
filter
filter 如同其名,可以用來過濾掉不要的 observables,例如下方的範例就會過濾的奇數的值。myArrayObservable .filter(new Func1<Integer, Boolean>() { @Override public Boolean call(Integer integer) { if (integer % 2 == 0) { return true; } return false; } }) .subscribe(new Action1<Integer>() { @Override public void call(Integer i) { Log.d("ACTION1", i.toString()); } });
處理 Asynchronous 任務
剛剛的任務都是執行在 UI Thread(Main Thread) 上的,如果我們要指定 observable 要在哪個 Thread 上執行任務,就要用 subscribeOn,subscribeOn(Schedulers.newThread) 或 subscribeOn(AndroidSchedulers.mainThread)。如果是要指定 observer 在那個 Thread 上接受資料,就要用 observeOn,如果沒有指名 observeOn,observer 就會在 subscribeOn 所指定的 thread 上接收到資料。只是要傳遞簡單的資料,我們可以用前面介紹的 just 和 from 方法,但大多情況我們都需要執行其他的方法後,才能取得資料,此時就可以用 create 來建立 Observable,並實作 OnSubscribe 介面。
Observable<String> fetchFromGoogle = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { try { String data = fetchData("http://www.google.com"); subscriber.onNext(data); // Emit the contents of the URL subscriber.onCompleted(); // Nothing more to emit }catch(Exception e){ subscriber.onError(e); // In case there are network errors } } });
zip
有時在處理網路任務時,會需要等待兩個以上的任務同時完成,並處理結果,這不但要添加不少程式碼,也容易寫錯,此時 ReactiveX 所提供的 zip 方法便帶來了這樣的好處:// Fetch from both simultaneously Observable<String> zipped = Observable.zip(fetchFromGoogle, fetchFromYahoo, new Func2<String, String, String>() { @Override public String call(String google, String yahoo) { // Do something with the results of both threads return google + "\n" + yahoo; } });
concat
concat 的功能和 from 很像,會把 observable 的結果一個接著一個回傳。Observableobservable1 = Observable.just("Hello"); Observable observable2 = Observable.just("World"); // Fetch from both simultaneously Observable.concat(observable1, observable2) .subscribe(new Subscriber<string>() { @Override public void onCompleted() {} @Override public void onError(Throwable e) {} @Override public void onNext(String s) { Log.d("Sean", s); } });
Ref: https://code.tutsplus.com/tutorials/getting-started-with-reactivex-on-android--cms-24387
2016年10月14日 星期五
[Git] 如何更改 push 後的 commit 內容?
How to change git commit message after push?
git commit --amend
然後在 force push 到 remote server:git push --force <repository> <branch>
如果同時也有人在修改該 branch 時,最好是用 --force-with-lease 比較安全,因為如果 upstream 已經有更改時,push 會被 abort:git push --force-with-lease <repository> <branch>
Ref: http://stackoverflow.com/questions/8981194/changing-git-commit-message-after-push-given-that-no-one-pulled-from-remote
2016年10月13日 星期四
[Android 4.4 Issue] TextView 無法垂直置中
The TextView cannot center vertically:
<TextView
android:id="@+id/toolbar_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
tools:text="Hello"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:textSize="@dimen/font_slarge"/>
It seems an OS issue that android:gravity="center_vertical" doesn't work when android:layout_height="match_parent". The issue can be fixed when giving the specific height.
Ref: https://code.google.com/p/android/issues/detail?id=59368
<TextView
android:id="@+id/toolbar_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
tools:text="Hello"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:textSize="@dimen/font_slarge"/>
It seems an OS issue that android:gravity="center_vertical" doesn't work when android:layout_height="match_parent". The issue can be fixed when giving the specific height.
Ref: https://code.google.com/p/android/issues/detail?id=59368
2016年10月3日 星期一
[Mac] 如何在 ZSH 下設定 adb 指令?
How to add ADB command to ZSH?
export PATH=$PATH:"$HOME/Library/Android/sdk/platform-tools"
2016年9月8日 星期四
[Mac] Leave the old terminal behind and try iTerm2+Oh My Zsh+Solarized color scheme
好用的 terminal tool,而且有較好的介面配色。
iTerm2 + Oh My Zsh + Solarized color scheme + Meslo powerline font (OS X / macOS)
https://gist.github.com/kevin-smets/8568070
iTerm2 + Oh My Zsh + Solarized color scheme + Meslo powerline font (OS X / macOS)
https://gist.github.com/kevin-smets/8568070
2016年6月24日 星期五
Touch Event 的機制
Android 在處理 touch event 的傳遞可以分 ViewGroup 跟 View 來看,
兩者皆都透過 dispatchTouchEvent() 來傳遞事件,而 ViewGroup 會先交由 onInterceptTouchEvent() 來做處理(line 1960),預設是 return false 不做處理(可以透過繼承來改寫),找到第一個可視的 child 後,再呼叫 dispatchTransformedTouchEvent() (line 2049)。
// ViewGroup.java Click here for full source.
可以看到 dispatchTransformedTouchEvent() 裡又會去呼叫 child 的 dispatchTouchEvent() (line 2405)。
// ViewGroup.java Click here for full source.
接著跳轉到 View 的 dispatchTouchEvent() 來看,可以發現View 會先處理 user 定義的 onTouch() 事件(line 8467),如果 user 不處理(返回 false),系統才會呼叫 onTouchEvent() (line 8471) 來進一步觸發常用的 onClick()或 onLongClick() callback。
// View.java Click here for full source.
兩者皆都透過 dispatchTouchEvent() 來傳遞事件,而 ViewGroup 會先交由 onInterceptTouchEvent() 來做處理(line 1960),預設是 return false 不做處理(可以透過繼承來改寫),找到第一個可視的 child 後,再呼叫 dispatchTransformedTouchEvent() (line 2049)。
// ViewGroup.java Click here for full source.
1928 @Override 1929 public boolean dispatchTouchEvent(MotionEvent ev) { 1930 if (mInputEventConsistencyVerifier != null) { 1931 mInputEventConsistencyVerifier.onTouchEvent(ev, 1); 1932 } 1934 // If the event targets the accessibility focused view and this is it, start 1935 // normal event dispatch. Maybe a descendant is what will handle the click. 1936 if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { 1937 ev.setTargetAccessibilityFocus(false); 1938 } 1940 boolean handled = false; 1941 if (onFilterTouchEventForSecurity(ev)) { 1942 final int action = ev.getAction(); 1943 final int actionMasked = action & MotionEvent.ACTION_MASK; 1945 // Handle an initial down. 1946 if (actionMasked == MotionEvent.ACTION_DOWN) { 1947 // Throw away all previous state when starting a new touch gesture. 1948 // The framework may have dropped the up or cancel event for the previous gesture 1949 // due to an app switch, ANR, or some other state change. 1950 cancelAndClearTouchTargets(ev); 1951 resetTouchState(); 1952 } 1954 // Check for interception. 1955 final boolean intercepted; 1956 if (actionMasked == MotionEvent.ACTION_DOWN 1957 || mFirstTouchTarget != null) { 1958 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 1959 if (!disallowIntercept) { 1960 intercepted = onInterceptTouchEvent(ev); 1961 ev.setAction(action); // restore action in case it was changed 1962 } else { 1963 intercepted = false; 1964 } 1965 } else { 1966 // There are no touch targets and this action is not an initial down 1967 // so this view group continues to intercept touches. 1968 intercepted = true; 1969 } 1971 // If intercepted, start normal event dispatch. Also if there is already 1972 // a view that is handling the gesture, do normal event dispatch. 1973 if (intercepted || mFirstTouchTarget != null) { 1974 ev.setTargetAccessibilityFocus(false); 1975 } 1977 // Check for cancelation. 1978 final boolean canceled = resetCancelNextUpFlag(this) 1979 || actionMasked == MotionEvent.ACTION_CANCEL; 1981 // Update list of touch targets for pointer down, if needed. 1982 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; 1983 TouchTarget newTouchTarget = null; 1984 boolean alreadyDispatchedToNewTouchTarget = false; 1985 if (!canceled && !intercepted) { 1987 // If the event is targeting accessiiblity focus we give it to the 1988 // view that has accessibility focus and if it does not handle it 1989 // we clear the flag and dispatch the event to all children as usual. 1990 // We are looking up the accessibility focused host to avoid keeping 1991 // state since these events are very rare. 1992 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() 1993 ? findChildWithAccessibilityFocus() : null; 1995 if (actionMasked == MotionEvent.ACTION_DOWN 1996 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) 1997 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 1998 final int actionIndex = ev.getActionIndex(); // always 0 for down 1999 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) 2000 : TouchTarget.ALL_POINTER_IDS; 2002 // Clean up earlier touch targets for this pointer id in case they 2003 // have become out of sync. 2004 removePointersFromTouchTargets(idBitsToAssign); 2006 final int childrenCount = mChildrenCount; 2007 if (newTouchTarget == null && childrenCount != 0) { 2008 final float x = ev.getX(actionIndex); 2009 final float y = ev.getY(actionIndex); 2010 // Find a child that can receive the event. 2011 // Scan children from front to back. 2012 final ArrayList<View> preorderedList = buildOrderedChildList(); 2013 final boolean customOrder = preorderedList == null 2014 && isChildrenDrawingOrderEnabled(); 2015 final View[] children = mChildren;
如果沒有自定義 children 的 draw 順序的話,default 就是越晚加入 ViewGroup 的 child 也晚畫,所以從最上層的 child 來開始接收事件,最上層不收就在往下一層丟,直到 child 處理事件就跳開迴圈(line 2049)。
2016 for (int i = childrenCount - 1; i >= 0; i--) { 2017 final int childIndex = customOrder 2018 ? getChildDrawingOrder(childrenCount, i) : i; 2019 final View child = (preorderedList == null) 2020 ? children[childIndex] : preorderedList.get(childIndex); 2022 // If there is a view that has accessibility focus we want it 2023 // to get the event first and if not handled we will perform a 2024 // normal dispatch. We may do a double iteration but this is 2025 // safer given the timeframe. 2026 if (childWithAccessibilityFocus != null) { 2027 if (childWithAccessibilityFocus != child) { 2028 continue; 2029 } 2030 childWithAccessibilityFocus = null; 2031 i = childrenCount - 1; 2032 } 2034 if (!canViewReceivePointerEvents(child) 2035 || !isTransformedTouchPointInView(x, y, child, null)) { 2036 ev.setTargetAccessibilityFocus(false); 2037 continue; 2038 } 2040 newTouchTarget = getTouchTarget(child); 2041 if (newTouchTarget != null) { 2042 // Child is already receiving touch within its bounds. 2043 // Give it the new pointer in addition to the ones it is handling. 2044 newTouchTarget.pointerIdBits |= idBitsToAssign; 2045 break; 2046 } 2048 resetCancelNextUpFlag(child); 2049 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 2050 // Child wants to receive touch within its bounds. 2051 mLastTouchDownTime = ev.getDownTime(); 2052 if (preorderedList != null) { 2053 // childIndex points into presorted list, find original index 2054 for (int j = 0; j < childrenCount; j++) { 2055 if (children[childIndex] == mChildren[j]) { 2056 mLastTouchDownIndex = j; 2057 break; 2058 } 2059 } 2060 } else { 2061 mLastTouchDownIndex = childIndex; 2062 } 2063 mLastTouchDownX = ev.getX(); 2064 mLastTouchDownY = ev.getY(); 2065 newTouchTarget = addTouchTarget(child, idBitsToAssign); 2066 alreadyDispatchedToNewTouchTarget = true; 2067 break; 2068 } 2070 // The accessibility focus didn't handle the event, so clear 2071 // the flag and do a normal dispatch to all children. 2072 ev.setTargetAccessibilityFocus(false); 2073 } 2074 if (preorderedList != null) preorderedList.clear(); 2075 } 2077 if (newTouchTarget == null && mFirstTouchTarget != null) { 2078 // Did not find a child to receive the event. 2079 // Assign the pointer to the least recently added target. 2080 newTouchTarget = mFirstTouchTarget; 2081 while (newTouchTarget.next != null) { 2082 newTouchTarget = newTouchTarget.next; 2083 } 2084 newTouchTarget.pointerIdBits |= idBitsToAssign; 2085 } 2086 } 2087 } 2089 ...
可以看到 dispatchTransformedTouchEvent() 裡又會去呼叫 child 的 dispatchTouchEvent() (line 2405)。
// ViewGroup.java Click here for full source.
2363 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 2364 View child, int desiredPointerIdBits) { 2365 final boolean handled; 2367 // Canceling motions is a special case. We don't need to perform any transformations 2368 // or filtering. The important part is the action, not the contents. 2369 final int oldAction = event.getAction(); 2370 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { 2371 event.setAction(MotionEvent.ACTION_CANCEL); 2372 if (child == null) { 2373 handled = super.dispatchTouchEvent(event); 2374 } else { 2375 handled = child.dispatchTouchEvent(event); 2376 } 2377 event.setAction(oldAction); 2378 return handled; 2379 } 2381 // Calculate the number of pointers to deliver. 2382 final int oldPointerIdBits = event.getPointerIdBits(); 2383 final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; 2385 // If for some reason we ended up in an inconsistent state where it looks like we 2386 // might produce a motion event with no pointers in it, then drop the event. 2387 if (newPointerIdBits == 0) { 2388 return false; 2389 } 2391 // If the number of pointers is the same and we don't need to perform any fancy 2392 // irreversible transformations, then we can reuse the motion event for this 2393 // dispatch as long as we are careful to revert any changes we make. 2394 // Otherwise we need to make a copy. 2395 final MotionEvent transformedEvent; 2396 if (newPointerIdBits == oldPointerIdBits) { 2397 if (child == null || child.hasIdentityMatrix()) { 2398 if (child == null) { 2399 handled = super.dispatchTouchEvent(event); 2400 } else { 2401 final float offsetX = mScrollX - child.mLeft; 2402 final float offsetY = mScrollY - child.mTop; 2403 event.offsetLocation(offsetX, offsetY); 2405 handled = child.dispatchTouchEvent(event); 2407 event.offsetLocation(-offsetX, -offsetY); 2408 } 2409 return handled; 2410 } 2411 transformedEvent = MotionEvent.obtain(event); 2412 } else { 2413 transformedEvent = event.split(newPointerIdBits); 2414 } 2416 // Perform any necessary transformations and dispatch. 2417 if (child == null) { 2418 handled = super.dispatchTouchEvent(transformedEvent); 2419 } else { 2420 final float offsetX = mScrollX - child.mLeft; 2421 final float offsetY = mScrollY - child.mTop; 2422 transformedEvent.offsetLocation(offsetX, offsetY); 2423 if (! child.hasIdentityMatrix()) { 2424 transformedEvent.transform(child.getInverseMatrix()); 2425 } 2427 handled = child.dispatchTouchEvent(transformedEvent); 2428 } 2430 // Done. 2431 transformedEvent.recycle(); 2432 return handled; 2433 }
接著跳轉到 View 的 dispatchTouchEvent() 來看,可以發現View 會先處理 user 定義的 onTouch() 事件(line 8467),如果 user 不處理(返回 false),系統才會呼叫 onTouchEvent() (line 8471) 來進一步觸發常用的 onClick()或 onLongClick() callback。
// View.java Click here for full source.
8439 public boolean dispatchTouchEvent(MotionEvent event) { 8440 // If the event should be handled by accessibility focus first. 8441 if (event.isTargetAccessibilityFocus()) { 8442 // We don't have focus or no virtual descendant has it, do not handle the event. 8443 if (!isAccessibilityFocusedViewOrHost()) { 8444 return false; 8445 } 8446 // We have focus and got the event, then use normal event dispatch. 8447 event.setTargetAccessibilityFocus(false); 8448 } 8450 boolean result = false; 8452 if (mInputEventConsistencyVerifier != null) { 8453 mInputEventConsistencyVerifier.onTouchEvent(event, 0); 8454 } 8456 final int actionMasked = event.getActionMasked(); 8457 if (actionMasked == MotionEvent.ACTION_DOWN) { 8458 // Defensive cleanup for new gesture 8459 stopNestedScroll(); 8460 } 8462 if (onFilterTouchEventForSecurity(event)) { 8463 //noinspection SimplifiableIfStatement 8464 ListenerInfo li = mListenerInfo; 8465 if (li != null && li.mOnTouchListener != null 8466 && (mViewFlags & ENABLED_MASK) == ENABLED 8467 && li.mOnTouchListener.onTouch(this, event)) { 8468 result = true; 8469 } 8471 if (!result && onTouchEvent(event)) { 8472 result = true; 8473 } 8474 } 8476 if (!result && mInputEventConsistencyVerifier != null) { 8477 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); 8478 } 8480 // Clean up after nested scrolls if this is the end of a gesture; 8481 // also cancel it if we tried an ACTION_DOWN but we didn't want the rest 8482 // of the gesture. 8483 if (actionMasked == MotionEvent.ACTION_UP || 8484 actionMasked == MotionEvent.ACTION_CANCEL || 8485 (actionMasked == MotionEvent.ACTION_DOWN && !result)) { 8486 stopNestedScroll(); 8487 } 8489 return result; 8490 }
訂閱:
文章 (Atom)