2018年12月6日 星期四

【Android】如何讓WebView大小自動擴展為內容的大小

WebViewClient的「onPageFinished」被呼叫執行時,就表示網頁已經有一個初步的大小可供計算。但從Java端並無法去取得這個到小,只有網頁本身可以知道自己的大小,所以我們必須要使用JavaScriptInterface去取得這個大小。

在「onPgaeFinished」內執行一段JavaScript:「"javascript:MyApp.resize(document.body.getBoundingClientRect().height)"」,功能大概是說「執行resize這個函數,並將網頁body的高度傳入。」

所以接著要設計一個名稱為「resize」的函數並掛上「@JavascriptInterface」。

函數的內容基本上是重新設定整個WebView的LayoutParams,尤其是寬高。

高度的算法大概是「height * getResources().getDisplayMetrics().density」,「height」大概就是在JavaScript中傳入「resize」函數的數值。因為網頁有自己的高度單位,所以要換算成density才能取得正確大小。


注意!要記得執行addJavascriptInterface這個功能。這是讓網頁內容可以取得加註了「@JavascriptInterface」函數的必要辦法。(記得「javascript」中的「MyApp」和這裡的「MyApp」是連動的,如果要修改或自訂,記得一起完成。)


以下是更完整的程式碼。
private void setupWebView() {
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            webView.loadUrl("javascript:MyApp.resize(document.body.getBoundingClientRect().height)");
            super.onPageFinished(view, url);
        }
    });
    webView.addJavascriptInterface(this, "MyApp");
}

@JavascriptInterface
public void resize(final float height) {
    MyActivity.this.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            webView.setLayoutParams(new LinearLayout.LayoutParams(getResources().getDisplayMetrics().widthPixels, (int) (height * getResources().getDisplayMetrics().density)));
        }
    });
}

2018年11月27日 星期二

[Android]使用DownloadManager

DownloadManager是個可以直接把檔案存進「下載區」、而不是在專案路徑下的工具。

它算是SystemService的一種,取得實體的方式跟其他Manager大同小異。(使用Activity下的getSystemService,並傳入Context.DOWNLOAD_SERVICE這個參數。)

但實際執行下載動作的不是DownloadManager,而是DownloadManager.Request。只要使用一般物件宣告實體的方式並指定一組網址參數給它,然後將它轉交給DownloadManager的「enqueue」就會開始執行下載動作。

下載完畢的時候,它會發出一個帶有「DownloadManager.ACTION_DOWNLOAD_COMPLETE」的Intent,所以這個工具要使用BroadcastReciever去接收這個Intent已完成「下載完成」的偵測。(這跟常見的Http管理工具風格有點相違背,因為這年頭習慣製作一個「事件」的Listener後,再把這個事件傳給Http的行為Thread。所以不習慣什麼事情都用BroadcastReciever的人在使用這個物件時可能會猶豫。

開始下載前(執行「enqueue」指令時), DownloadManager會回傳一組長整數(Long):fileID。在BroadcastReciever中,要使用這個fileID去取得「下載完的檔案實體路徑」。(顯然Google注意到了並不是每家廠牌手機都使用相同的路徑設定,下載區有可能會有很獨特的路徑,這會導致APP崩潰。)

以下功能寫在BroadcastReciever中。(範例是個圖檔,所以使用ImageView顯示。)

DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadId); Cursor c = manager.query(query); if (c.moveToFirst()) { int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String uriString = c.getString(
c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); imageView.setImageURI(Uri.parse(uriString)); Toast.makeText(MainActivity.this,
"download success",
Toast.LENGTH_SHORT).show(); } }

它提供了一個快速打開「檔案區」的方法。
Intent i = new Intent(); i.setAction(DownloadManager.ACTION_VIEW_DOWNLOADS); startActivity(i);

但一般來說都會希望自動導引使用專門開啟檔案的APP來瀏覽檔案。

要讓「其他APP」瀏覽「檔案」從API:24以後都強制使用FileProvide。

FileProvider不用複寫任何功能,只需擴充就可以發揮功用。(這樣做的意義何在?研究中。)






2018年10月30日 星期二

演算法練習「Candidate Report: Anonymous」

解答...


這題是「Stacks and Queues」的第一題練習。

簡單來說就是「後進先出」:用一個矩陣逐一紀錄並疊加收到的資料。

例如:資料1收在矩陣0,資料2收在矩陣1...資料N收在矩陣N-1,但等到要處理資料時,先從資料N、資料N-1...逐一開始往回處理,如果臨時又有資料加入,則繼續疊加程序。


這跟一般來說的「排隊」不同,這是文本或編譯器的基礎。

例如JSON翻譯器,或程式碼編譯時用來辨識「區段」的開始與結束,例如for迴圈或類別檔的內容。

本身反而沒難度,搞懂邏輯程序、確保題目結果正確後,效能並沒有什麼難度。(因為題目本身就是個一維迴圈。)

2018年10月29日 星期一

演算法練習 「GenomicRangeQuery」 (快速計算矩陣內單位總和的方式)

題目和解法...

講義...

一連串長度為X的數值矩陣,如何知道單位M和N之間有不為零的數值?(M>N,且絕對不小於零或大於等於X,所以M和N單位絕對都在矩陣內。)


這其實是個變相計算總和的技巧。
如果把矩陣從頭(最小單位)跑一次迴圈,並且每次都把前一個單位的值和自己做加總,例如「X[A] = X[A] + X[A-1]」,則等到迴圈跑完,這個迴圈的每個單位(i)會變成「從0到i單位的總和」,如果要知道每個單位原本的值,只需要把單位(i)減去單位(i-1)就好。
如果要問「i到j之間單位的數值總和(假設j > i)」,也只需要把單位(j)減去單位(i)就好。



所以如果題目要知道ATCG在基因序列中出現的頻率,那就宣告四個和基因序列等長的矩陣,個別對應到ATCG,每次只要出現相對應的序列,就在那個矩陣單位中加一(X[A]=1)。

如果C[i] = N,而C[i + a] = N+2,這就表示在「i」到「i+a」這中間的單位裡,C出現了兩次。這個C[i] - C[i+a]如果稱為F(C, i, 2),則按照題目的要求,F(A, i, j)不為0時答案為1,F(C, j, j)不為零時答案為1......

2018年8月2日 星期四

【Android】DialogFragment使用的小秘訣

DialogFragment並不是Dialog,所以習慣上設定Dialog的很多細節都無法直接使用。

設計上(我發現)矛盾的地方在於它確實會產生Dialog類別,但這個類別的產生又限定在MainUIThread中,這會導致「不同時間點使用DialogFragment時會產生Error導致Crash」,但Fragment的使用自由度高、調控會由底層安排,這點似乎在DialogFragment上有漏洞。

要徹底避免,實作DialogFragment時就要使用onCreateView、而不是onCreateDialog。

但這會導致無法使用Dialog的功能,而只能製作setView的內容。

解決方法是使用DialogFragment的getDialog......對!DialogFragment下有這個功能。

但同樣的矛盾問題再次發生:要被MainUIThread執行過後才會有Dialog,在這之前執行getDialog只會得到空值。

所以要注意將整段功能(需要使用到Dialog)的地方丟入MainUIThread中執行、或乾脆等個幾毫秒鐘再繼續執行...(無限判斷是否已經有Dialog。)

另外,FragmentManager要在MainUIThread執行過 executePendingTransactions() ,Dialog才會是「可以被修改的」。(不然,要設個ClickListener?不行。) 

2018年7月23日 星期一

【Android】尋找特定View在ScrollView中的位置並滾動到定位

ScrollView中必須要再包一個「ViewGroup」才有辦法再放入其他View。(它只能容納一個ChildView。)

所以「(特定View)在ScrollView中的位置」其實是「(特定View)在此ViewGroup中的位置」。

但一般尋找「View在螢幕中絕對座標」的辦法無法用在此處。

以縱軸座標的ScrollView來說,「位置」其實是指「這個View距離ViewGroup上方邊界的距離」。

所以要使用View中的功能:getTop(),來取得.......

但這個功能取得的是View跟最接近的ViewGroup的距離。也就是說如果ScrollView和View之間不只一層ViewGroup,而是有多層ViewGroup,(為了做出複雜的版型,)那就必須要用一個迴圈將它們的「getTop()」通通取出來作加總...

舉例.....

假設ScrollView和View之間有ViewGroup(0)、ViewGroup(1)、ViewGroup(2)、ViewGroup(3),最後才是View,....如果已經可以清楚知道ViewGroup的數目,那就好辦,但如果不知道...

要從View取ParentView,(這東西可以直接轉型成「View類別」,)然後再從ParentView取ParentView...直到取出來的ParentView是ScrollView為止。

每次的getTop記得都要累加起來,得到的數值才是畫面上、視覺上,這個View在ScrollView中的位置。

2018年7月20日 星期五

【Java】Instanceof 的效能

Interface可以讓「無直接繼承關係」的物件之間共享相同屬性,──簡單來說,介面本身就是種屬性。

判讀這種屬性用的就是「instanceof」這個指令。


舉例:「設置一個介面稱為『Wing』,然後在怪物A的子怪物類別群中找到一隻,假設類別為怪物A2B,然後讓它擴充『Wing』介面。」如此一來,在一系列怪物中就可以跳過在「怪物A」、甚至「怪物A的父類別」中新增參數「Wing」。──雖然不一定會發生,這樣做的好處是「設計上彈性非常高」。

但是相較於判讀介面,判讀物件類別名稱只要十分之一的效能。(而且判讀物件類別名稱所消耗的效能又是直接判讀參數的一到三倍。)但最終物件類別名稱並無法達到我剛才所說的效果。

把所有都擴充了Wing介面的類別都集合在一起判斷,施作上不合理!(但......實際上可以這樣做。)


將所有屬性都集中在一個父類別,然後用子類別來「變化/初始化」這些參數,這在講求效能的情況下還是比較實在。但怎麼設計參數........每多增加一個參數,類別的物件實體在記憶體中占用的量就會增加。

所以一般來說都是使用位元運算子,一個短整數等於8bit,也就等於八個boolean值,直接宣告一個短整數省記憶體?還是宣告八個boolean省記憶體?...以後再測試。