Search

PTT 行動版無廣告瀏覽器 - PiTT

2015-11-27 12:03 PM


全新無廣告行動版 PTT 瀏覽器登場囉! 用手機也可以輕鬆逛 PTT!

行動裝置上最懂你的 PTT 瀏覽器,心目中最理想的行動版 PTT 就該像這樣! I SEE YOU

立即前往 Google Play 下載!

讚不絕口的獨家特色

- 無廣告! 無廣告! 無廣告!
因為很重要所以說三次,
在此保證在功能 "做好做滿" 之前不會置入廣告!
置入廣告前的幾個版本更新會通知各位使用者,不更新就永遠不會有廣告!

- ANSI/MOBI 自由切換模式
看 ANSI ART 不需重新載入文章內容消耗行動網路流量,
讓你輕鬆看鄉民的藝術天分有多高!
ANSI 模式(其他 APP 稱為整頁模式) 可以等寬文字進行瀏覽
MOBI 模式則是以最佳化的行動版介面顯示

- 帳號任意門
其實全PTT只有我一個人,不信我換個帳號推一樣的話給你看。
不論是在最愛列表,文章列表還是文章內容,
你愛怎麼換帳號就怎麼換帳號。

- 推文進度報馬仔
打了一大堆推文卻不知到要推到何年何月?
獨家推文進度顯示告訴你還有幾則推文要繼續推
也告訴你還要等幾秒才可以繼續推
推到一半不想推還可以隨時中斷
直到你離開文章內容頁之前,推文暫存會為了你一直存在!
剩餘推文字數就不用說了,你懂的~

- 發文暫存功能
就算你關機了我也可以幫你回復你放棄編輯的文章!
文章編輯到一半突然想看八卦?
放心退出文章編輯模式去看吧!
我已經把你的文章內容都放在資料庫裡了!

- 發文 Redo/Undo 功能
複製貼上發現貼錯了...
刪除到不該刪的段落了...
乖,按一下 Redo/Undo 就通通幫你恢復原樣
比哆啦A夢的時光包巾還好用

- 文章瀏覽紀錄
忘記推了哪篇文章想看又找不到?
經典大長篇卻忘記標題是什麼?
不只記錄看板,你看過的文章通通幫你記錄下來
不用消耗行動網路流量也可以輕鬆追文

- 正/反向文章閱讀順序
文章太長想 End?
End 後發現是長篇經典文章又想從頭看?
沒問題! Home/End 切換讓你不管從上到下還是從下到上都滑得精彩!

接下來就來看看獨家功能的使用教學

文章編輯介面

Redo/Undo 可以記錄最多50筆編輯紀錄

只要按下下方工具列的 Redo/Undo 即可輕鬆回復編輯紀錄

讓你不再為行動版編輯而困擾



暫存文章回復功能可以在你放棄編輯時自動儲存文章內容

只要回到文章編輯頁面即可自動偵測是否有上次放棄編輯的文章內容

就算 App 曾經被關閉也可以回復

再也不怕辛苦打字的心血付諸東流



貼心防呆功能

按兩次返回鍵才會關閉文章編輯頁面

不須按其他確認離開按鈕

讓你輕鬆離開編輯介面又不怕按錯

就算真的按錯也有文章恢復功能

安心,放心又貼心

文章瀏覽

無邊框瀏覽模式,往下滑動自動隱藏占空間的工具列

往上滑動自動出現,讓你輕鬆瀏覽無負擔



ANSI/MOBI 模式自由切換(ANSI 模式在其他 App 稱為整頁模式)

看鄉民的藝術不再需要花兩倍流量

文章太長想直接 End 也沒問題

反向順向瀏覽隨你高興

看新推文只要按 End 重新整理即可看見鄉民最新回應

一鍵多工的按鍵設計讓你踏入鄉民的思考領域




推文分段預覽功能

懶得自己分段卻又不知道自動分段結果到底長怎樣?

按一下預覽分段讓你知道推文結果長怎樣



帳號任意門

讓你不管在看板頁,文章列表頁還是看文章推文

你愛怎麼換帳號就怎麼換帳號

其實全 PTT 只有我一個人,不信我換個帳號推一樣的話給你看

後記

開發這款 App 沒有想像中那麼容易

一來這是我的第一個 Android 專案

二來網路上的資源實在不夠多

光是 ANSI 控制碼還有資料傳輸規則就讓我崩潰了好幾次

原本只想花兩個月就完成,到現在做了快五個月

雖然中間有很多時間是在做介面的視覺設計,有時一調一整天就過去了...

於是為了不讓其他開發者遇到跟我一樣的困擾

在這幾天我會發布幾篇開發 PTT 行動版的教學文章

讓各位有興趣的開發者可以一起加入

讓 PTT 行動版有更好的瀏覽體驗

畢竟有競爭才會有進步

希望大家一起努力來提升台灣的軟體競爭力

Java - JSON 資料解析基本教學

2015-11-19 1:53 PM

JSON 算是目前主流的資料交換格式

跟 XML 比較起來 JSON的優點就是檔案小很多

但可讀性卻一點也不輸給 XML(這裡的可讀性是指"人")

以下就來簡單介紹 JSON 格式 以及如何解析資料

將 JSON 字串轉為物件 或是將物件轉換為 JSON 字串

在這裡會用到 Google 維護的 Gson Library

程式碼範例
// 陣列表達
[string1, string2, string3]

// 物件表達
{key1 : value1, key2 : value2, key3 : value3}

// 基本JSON表達式
{
  "id": 1,
  "name": "Hello JSON",
  "friends": [
    {
      "id": 2,
      "name": "Hello JAVA",
    },
    {
      "id": 3,
      "name": "Hello Andoird",
    }
  ],
  "certified": true
}

// 將 JSON 字串轉換為物件
return new GsonBuilder()
            // 若不須將日期自動轉換為 Date 物件則不須加入 setDateFormat 方法
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .create()
            // json 為 JSON字串, YourJsonObject.class 就是要轉換為的物件類型
            .fromJson(json, YourJsonObject.class);

// 若物件內的名稱與 JSON 內的名稱有些許差異時
// 可以用 @SerializedName("field_name") 指定
@SerializedName("issued_at")
private String fieldName;

// 若想轉換為 Map 格式 可使用以下方式 
Type t = new TypeToken<Map<String,Machine>>() {}.getType();
Map<String,Machine> map = (Map<String,Machine>) new Gson().fromJson("json", t);

// 若 JSON 內容最外層為陣列 而想轉換為 List 時可使用以下方式
Type t = new TypeToken<List<SearchResult>>() {}.getType();
List<SearchResult> list = (List<SearchResult>) new Gson().fromJson("json", t);

// 將物件轉為 JSON 格式字串
return new GsonBuilder()
            // 指定 Gson 不預先 escape html 的某些字元(如 "<", ">")
            .disableHtmlEscaping()
            .create()
            .toJson(this);
各項資料連結
Android - HttpURLConnection 基本教學 取得網頁資料(HTML, XML, JSON)
Android - 使用 HttpURLConnection 自動轉址失效的解決方式
google/gson - GitHub
JSON - MDN

Android - UncaughtException 很抱歉, app 已停止的錯誤處理

2015-11-18 6:00 PM

有時在實機測試會遇到 很抱歉,XXX已停止的錯誤並跳開應用程式

這種情況只靠 Log4J 是抓不到錯誤訊息的

因此 Debug 就非常困難 沒有錯誤訊息根本不知道哪裡有問題

那麼要如何取得相關的錯誤訊息呢?

由於是未處理的 Exception (稱作 UncaughtException)

因此會直接拋到 UI Thread 然後報錯 應用程式就停止了

要處理這類 UncaughtException 只要在 Application 加入以下程式碼即可

程式碼範例

public class App extends MultiDexApplication {

    // 建立 Logger 將以此輸出錯誤訊息
    private final Logger logger = Logger.getLogger(App.class);

    // 建立 UncaughtExceptionHandler UI Thread 的錯誤訊息會拋給這個物件處理
    private Thread.UncaughtExceptionHandler androidDefaultUEH;
    private Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
        public void uncaughtException(Thread thread, Throwable ex) {
            // print 錯誤訊息
            logger.error("Uncaught exception is:", ex);
            // 回報給預設錯誤處理
            androidDefaultUEH.uncaughtException(thread, ex);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();

        // 初始化 Log4J
        ConfigureLog4J.configure();

        // 建立 Handler 並指定為預設處理 Handler
        androidDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(handler);
    }
}

各項資料連結
Android Handling the Unexpected
Android Developers - Thread.UncaughtExceptionHandler

Android - ObjectAnimator 的 Easing 進階動畫效果

2015-11-17 3:37 PM

使用 ObjectAnimator 實現動畫效果 這篇文章裡

我們學會了如何使用 Android 原生的 ObjectAnimator 實現動畫效果

在這裡我們要用第三方的 Framework 實現一些較進階的動畫效果

若有做過介面相關特效的人員應該很熟悉一個叫做 Easing 的函數

但預設的 ObjectAnimator 能用的函數很少 而且名稱也不同

於是我們就須使用以下兩個開源的 Libraries

JakeWharton/NineOldAndroidsdaimajia/AnimationEasingFunctions

達成可指定 Easing 函數 以及同時運行多個動畫參數的效果

以下將以原本隱藏在畫面右方的選單元件往左滑動顯示的特效作為範例

程式碼範例
// 首先請先在專案加入這兩個 dependency
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.daimajia.easing:library:1.0.1@aar'

// 在這裡使用的都是第三方的類別 名稱都跟原生 API 長得一模一樣 千萬不要搞錯
import com.daimajia.easing.Glider;
import com.daimajia.easing.Skill;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;

// 若之前狀態為 Gone 將無法取得寬度 因此在設為 Visible 後再以 AsyncTask 取得寬度設定動畫
mainMenuHolder.setVisibility(View.VISIBLE);

mainMenuHolder.post(new Runnable() {
    @Override
    public void run() {
     // 由於此範例是在 Fragment 內執行 因此需呼叫 getActivity 才能取得畫面尺寸
        Display display = getActivity().getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        int width = size.x;

        // 建立動畫物件 將選單由最右方向左滑出 直至顯示完整元件
        ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(mainMenu, "x", width - mainMenu.getWidth());
        // 建立動畫物件 將選單外的畫面變暗的效果
        ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(mainMenuMask, "alpha", 1f);

        // 建立動畫監聽事件 在動畫開始時設定為 VISIBLE
        // 這是由於在隱藏選單時同樣會加入此監聽事件 並在動畫結束時隱藏的關係
        objectAnimator1.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart (Animator animation){
                mainMenu.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd (Animator animation){
            }

            @Override
            public void onAnimationCancel (Animator animation){
            }

            @Override
            public void onAnimationRepeat (Animator animation){
            }
        });

        // 將剛剛建立的動畫加入動畫陣列
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                // 在此可設定 Easing 的函數 有開發過互動式介面的應該很熟悉
                // 若不清楚有那些可以設定 差別又在哪裡 可以看下方的 Easing Functions 參考連結
                Glider.glide( Skill.QuadEaseOut, 300, objectAnimator1 ),
                Glider.glide( Skill.QuadEaseOut, 300, objectAnimator2 )
        );

        // 設定動畫時間間隔 請以動畫陣列內執行時間最長的為主
        set.setDuration(300);
        // 開始執行動畫
        set.start();
    }
});
各項資料連結
Android - 使用 ObjectAnimator 實現動畫效果
Easing Functions
GitHub - JakeWharton/NineOldAndroids
GitHub - daimajia/AnimationEasingFunctions
Android - ObjectAnimator

Android - 使用 ObjectAnimator 實現動畫效果

12:51 PM

之前在開發 Flash 及 Javascript 上的介面時

我習慣用 GreenSock 開發的 TweenLite Library

因為他的執行效率非常好 而且使用也很方便直覺

但可惜的是他沒有支援 Android

而在另一篇介紹的 TweenEngine 雖然使用上很類似

詳見 Android - 使用 Tweener 加入互動特效

可是常常會出現動畫效果出不來或是直接報錯給你看

於是就萌生了使用原生 Android 動畫 API 的想法

以下就來介紹 ObjectAnimator 的使用方法

這裡將以淡出 View 為範例 並在完全淡出後設定 Visible 為 GONE

程式碼範例
// 建立動畫操作物件
ObjectAnimator objectAnimator = ObjectAnimator
                    // 指定動畫效果
                    .ofFloat(articleAuthorDataHolder, "alpha", 1.0f, 0.0f)
                    // 指定動畫時間長度
                    .setDuration(300);

// 加入動畫監聽 
objectAnimator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {

    }

    // 動畫執行完成後將 View 設定為 GONE
    @Override
    public void onAnimationEnd(Animator animation) {
        articleAuthorDataHolder.setVisibility(View.GONE);
    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
});

// 開始動畫
objectAnimator.start();

各項資料連結
Android - 使用 Tweener 加入互動特效
Android - ObjectAnimator

Java - Thread 執行緒的 lock, wait, 與 notify

2015-11-09 6:17 PM

在使用多執行緒我們常常需要使用到 synchronize 確保值的一致性

但是有一些情況很容易造成資料錯亂

這時我們就必須將某一些 Thread lock 住

直到確認其他 Thread 的前置工作完成了 才執行其他 Thread 的後續工作

以下就來介紹執行緒的 lock 該如何設計

本範例將以同一個 class 內的多個執行緒做個簡單示範

程式碼範例
// 宣告一個 final 的變數 將作為參數傳遞至各執行緒
private final Object locker = new Object();

// 宣告另一個變數 作為判斷是否需要 lock 執行緒
// 至於在各執行緒間的傳遞方式有很多種 在這裡就不多作介紹
private boolean commandLock = false;

// Thread 1
while(true){
    if( !isConnected() ){
        lostConnection = true;
        break;
    }

    // 設定為 lock 狀態
    commandLock = true;

    // Do something

    // 設定為 unlock 狀態 並喚醒上一個等待中的Thread
    // 注意此處的 synchronized 是確保同時間只有一個執行緒操作 locker 物件
    commandLock = false;
    synchronized (locker) { locker.notify(); }
}

// Thread 2
public void reloadPage(){
    commandLocker();
    command( "Do reload" );
}

// Locker
private void commandLocker(){
    // 檢查 lock 狀態 若為 lock 狀態則進行 wait 動作
    // 直至 lock 狀態結束
    synchronized (locker) {
        while(commandLock){
            try {
                locker.wait();
            } catch (InterruptedException e) {
                logger.error("Thread sleep error in command locker section.");
            }
        }
    }
}

可能有人會看過在 while loop 中不使用 obj.wait(); 的作法

而是使用 Thread.sleep(millis); 的方式

但此方式容易消耗過多的效能進行不必要的 loop 執行

而且若 millis 沒有設定好 若同時有其他 Thread.sleep(); 的動作

很有因為時間差而可能造成 locker 永遠不會被解除的問題

但若單純使用空的 while 那效能的浪費是非常可怕的

因此還是使用 synchronized 加上 locker 物件的方式會較適當

各項資料連結
Java - Synchronized

Android - AlertDialog 的使用與建置

2015-11-08 11:06 AM

Android 的 AlertDialog 是一個非常好用的提示視窗元件

初始化非常的方便 且可應付大多數的情況

可以修改提示字元 確認按鈕字元 取消按鈕字元

設置 Listener 也相當的簡便

以下就來介紹該如何使用這個方便的控制元件

程式碼範例
// 建立元件
deleteAlert = new AlertDialog.Builder(this);
// 設置提示標題
deleteAlert.setTitle("貼心小提醒");
// 設置提示內容
deleteAlert.setMessage("確定要刪除嗎?");
// 設置確認按鈕顯示文字與 Listener
deleteAlert.setPositiveButton("確定", onClickPositiveDeleteArticle);
// 設置取消按鈕顯示文字與 Listener
deleteAlert.setNegativeButton("取消", onClickNegativeDeleteArticle);
// 設置使用者按下提示元件的其他地方與按下返回鍵導致取消的 Listener
deleteAlert.setOnCancelListener(onCancelDeleteArticle);

// 按下確認的 Listener
private DialogInterface.OnClickListener onClickPositiveDeleteArticle = new DialogInterface.OnClickListener(){
    @Override
    public void onClick(DialogInterface dialog, int which) {
        // TODO: Do something
    }
};

// 按下取消的 Listener
private DialogInterface.OnClickListener onClickNegativeDeleteArticle = new DialogInterface.OnClickListener(){
    @Override
    public void onClick(DialogInterface dialog, int which) {
        // TODO: Do something
    }
};

// 按下提示視窗之外的部分與按下返回鍵的 Listener
private DialogInterface.OnCancelListener onCancelDeleteArticle = new DialogInterface.OnCancelListener(){
    @Override
    public void onCancel(DialogInterface dialog) {
        // TODO: Do something
    }
};
各項資料連結
Android - AlertDialog

Android - 顯示 EditText 的虛擬鍵盤

2015-11-04 6:20 PM

若要在顯示某個 view 時

或是在按下其他按鈕時自動顯示指定 EditText 的虛擬鍵盤

可以使用以下方式

隱藏虛擬鍵盤使用方式詳見此篇介紹

Android - 隱藏 EditText 的虛擬鍵盤

程式碼範例
private EditText articleContentCommentEditor;

// 取得 focus
articleContentCommentEditor.requestFocus();
// 顯示虛擬鍵盤
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(articleContentCommentEditor, InputMethodManager.SHOW_IMPLICIT);
各項資料連結
Android - 隱藏 EditText 的虛擬鍵盤
Android Developer