Tuesday, August 5, 2014

ListView進階版 - RecyclerView使用實例(以Android Studio為例)

文章攢寫時間︰2014/08/05 13:43
文章修改時間︰2014/08/06 10:25
文章修改次數︰2

文章參考來源

1.RecyclerView example(國外網站)
2.RecyclerView in Android: The basics(國外網站)
3.RecyclerViewExtensions on Github
4.Android Simple RecyclerView Widget Example(國外網站)
5.ANDROID L: RECYCLERVIEW TUTORIAL(國外網站)
6.RecyclerView | Android Developer

本篇適合

對Android ListView元件了解的開發者

需要手機

Android 4.0 (Ice cream sandwich)以上

本篇概要

1.學會在Android Studio 0.8.x版添加Google擴充套件
2.學會使用RecyclerView
3.學會使用強大的載圖工具Picasso

一、前言


Google在I/O 2014發表了Android L,
同時也在該系統中推岀一個嶄新的顯示元件RecyclerView,
官方也宣稱該元件比原來的ListVIew更好用。


以下是該元件與原來的ListView不同的地方︰

  • 比ListView更進階也具彈性
  • ViewHolder變成強制性必須實作的類別(稍候看實例會理解)
  • 回收的速度比以往更有效率
  • 以前你只建立ListView和Adapter,現在你還多需要建立一個LayoutManager
  • LayoutManager能幫你避免過多次呼叫findViewById的所造成的資源浪費

二、本文開始

新元件LayoutManager和ItemAnimator介紹

在技術開始前,先說說LayoutManager這個新元件。

LayoutManager屬於RecyclerView的內部元件之一,其目的用來決定RecyclerView當一個view不再顯示給使用者,要怎麼重新使用這些view資源。

無論是要重覆使用(reuse)或資源回收(recycle)一個view,LayoutManager都會從數據集(Dataset)去讀取岀需要的資料,並且取代原view來顯示給使用者。然用這種方式去更換view能避免創建一些不需要的view、也能增進使用findViewById找資源的效能。

目前RecyclerView提供了LinearLayoutManager這個實作類別,該類別繼承了LayoutManager,這個元件能顯示直向版面(vertical)或橫向版面(Horizontal)的列表滑動清單。如果你需要一個客製化的排版方式(譬如想做得像以前的GridLayout方格式版面),你就得自己繼承RecyclerView.LayoutManager這個類別來教RecyclerView怎麼排版。

題外話,RecyclerView還提供動畫特效的功能,可以讓每一筆view顯示時,有一些被新增或移除的特效。如果想要改變這些特效,那麼就得去繼承RecyclerView.ItemAnimator這個類別,並在RecyclerView被實體化時,呼叫RecyclerView.setItemAnimator()這個函式。

技術環境準備

在開始coding以前,您需要準備好以下環境
  • Android Studio(0.8.x版以上)
  • 安裝SDK Tools、Platform-tools、Build-tools
  • 安裝Android Support Library和Android Support Repository
以上所需套件下載完畢後,請確認您電腦裡Android SDK目錄底下有以下資源

開始Coding

(1)建立一個新的App
  • Application name: TestRecyclerView
  • Company Doamin: com.android.recyclerview
  • 點選 Next
  • Minimum SDK: API 15
  • 點選 Next
  • 選擇 Blank Activity
  • 點選 Next
  • 點選 Finish
(2)修改編譯使用版號及添加所需Library
  • 點擊[File]->[Project Structure]
  • 點擊左側[Modules]->[app]
  • 右側頁面選擇[Properties],並修改以下值Compile Sdk Version: API20 / Build Tools Version: API 20.0.0
  • 右側頁面選擇[Flavors],並修改以下值Target SDK: API 20
  • 右側頁面選擇[Dependencies],並按下頁面下方[+]號,選擇1.Library dependency
  • 添加以下5個擴充套件

    com.android.support:appcompat-v7:+
    com.android.support:support-v4:+
    com.android.support:palette-v7:+
    com.android.support:recyclerview-v7:+
    com.squareup.picasso:picasso:2.3.+

    小註︰看到上面5個套件後面都顯示 .+ ,這個符號意謂著跟Gradle說「請幫我使用該套件的最新版本。
    但因為現在(2014/08/05)support-v4最新版本僅提供Android L Preview執行使用,因此我們需要做接下來的修改讓非Android L的手機也能使用這些套件。

  • 為避免專案編譯時遇到
  • Error:Execution failed for task ':app:processDebugManifest'.
  • > Manifest merger failed : uses-sdk:minSdkVersion 15 cannot be smaller than version L declared in library com.android.support:appcompat-v7:21.0.0-rc1


  • 我們需要修改二個地方︰

    1>>修改Gradle腳本app\build.gradle
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:+'
        compile 'com.android.support:support-v4:+'
        compile 'com.android.support:palette-v7:+'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
    }

     請改為

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:20.0.0'
        compile 'com.android.support:support-v4:20.0.0'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
        compile 'com.android.support:palette-v7:+'
    }

     這是build.gradle最後的樣子
    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 20
        buildToolsVersion '20.0.0'
    
        defaultConfig {
            applicationId "recyclerview.android.com.testrecyclerview"
            minSdkVersion 15
            targetSdkVersion 20
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                runProguard false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:20.0.0'
        compile 'com.android.support:support-v4:20.0.0'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
        compile 'com.android.support:palette-v7:+'
    }
    

    2>>添加以下3行code至app\src\AndroidManifest.xml
xmlns:tools="http://schemas.android.com/tools"
<uses-sdk tools:node="replace"></uses-sdk>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>

這是AndroidManifest.xml裡最後的樣子

按下Run ,試看看專案是否能正常在手機執行,
這麼做可以讓我們確定我們在coding前的初步環境和所需套件是否皆已建置完畢。
如果套件皆引用成功,應該能看到這個完全空白的畫面

(3)添加或修改3個Java class

  1. 添加ItemData這個Object類
  2. package recyclerview.android.hmkcode.com.recyclerview;
    
    /**
     * Created by lp43 on 2014/8/4.
     */
    public class ItemData {
        private String title;
        private String imageUrl;
    
        public ItemData(String title,String imageUrl){
    
            this.title = title;
            this.imageUrl = imageUrl;
    
        }
    
        public String getTitle() {
            return title;
        }
    
        public String getImageUrl() {
            return imageUrl;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public void setImageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
        }
    }
    

  3. 實作RecyclerView的Adapter-->MyAdapter.java
  4. package recyclerview.android.hmkcode.com.recyclerview;
    
    import android.support.v7.widget.RecyclerView;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    import com.squareup.picasso.Picasso;
    
    /**
     * Created by lp43 on 2014/8/4.
     */
    public class MyAdapter extends RecyclerView.Adapter{
        private ItemData[] itemsData;
    
        public MyAdapter(ItemData[] itemsData) {
            this.itemsData = itemsData;
        }
    
    
    
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            // create a new view
            View itemLayoutView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_layout, null);
    
            // create ViewHolder
    
            ViewHolder viewHolder = new ViewHolder(itemLayoutView);
            return viewHolder;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder viewHolder, int position) {
            // - get data from your itemsData at this position
            // - replace the contents of the view with that itemsData
    
            viewHolder.txtViewTitle.setText(itemsData[position].getTitle());
    //        viewHolder.imgViewIcon.setImageResource(itemsData[position].getImageUrl());
    
            Picasso.with(viewHolder.imgViewIcon.getContext()).cancelRequest(viewHolder.imgViewIcon);
            Picasso.with(viewHolder.imgViewIcon.getContext()).load(itemsData[position].getImageUrl()).into(viewHolder.imgViewIcon);
        }
    
        // Return the size of your itemsData (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return itemsData.length;
        }
    
        // inner class to hold a reference to each item of RecyclerView
        public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    
            public TextView txtViewTitle;
            public ImageView imgViewIcon;
    
            public ViewHolder(View itemLayoutView) {
                super(itemLayoutView);
                itemLayoutView.setOnClickListener(this);
                txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
                imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
            }
    
            @Override
            public void onClick(View view) {
                Toast.makeText(view.getContext(), "position = " + getPosition(), Toast.LENGTH_SHORT).show();
            }
        }
    }
    

  5. 在主頁面MyActivity.java實例化RecyclerView
  6. package recyclerview.android.hmkcode.com.recyclerview;
    
    import android.support.v7.app.ActionBarActivity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.support.v7.widget.DefaultItemAnimator;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    
    
    public class MyActivity extends ActionBarActivity {
        private String[] sources = {
                "http://lorempixel.com/600/250/",
                "http://lorempixel.com/600/250/sports",
                "http://lorempixel.com/600/200/sports/Dummy-Text",
                "http://lorempixel.com/600/200/nature",
                "http://lorempixel.com/600/200/food",
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_my);
    
            // 1. get a reference to recyclerView
            RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    
            // this is data fro recycler view
            ItemData[] itemsData = {
                    new ItemData("Delete",sources[0]),
                    new ItemData("Cloud",sources[1]),
                    new ItemData("Favorite",sources[2]),
                    new ItemData("Like",sources[3]),
                    new ItemData("Rating",sources[4]),
                    new ItemData("Delete",sources[0]),
                    new ItemData("Cloud",sources[1]),
                    new ItemData("Favorite",sources[2]),
                    new ItemData("Like",sources[3]),
                    new ItemData("Rating",sources[4])
            };
    
            // 2. set layoutManger
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
            // 3. create an adapter
            MyAdapter mAdapter = new MyAdapter(itemsData);
            // 4. set adapter
            recyclerView.setAdapter(mAdapter);
            // 5. set item animator to DefaultAnimator
            recyclerView.setItemAnimator(new DefaultItemAnimator());
    
        }
    
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.my, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
            if (id == R.id.action_settings) {
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
    
    
(4)drawable資料夾添加一個xml讓列表按下後有反饋
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
        android:drawable="@drawable/border2_pressed" />
    <item android:drawable="@drawable/border2" />
</selector>

border2_pressed.png

border2.png

(5)添加或修改layout資料夾的二個xml

  1. activity_my.xml
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context=".MyActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
    </RelativeLayout>
    
    
  3. item_layout.xml
  4. <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="40dp"
        android:background="@drawable/border2_combine">
    
        <!-- icon -->
        <ImageView
            android:id="@+id/item_icon"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="1dp"
            android:layout_marginBottom="1dp"
            android:contentDescription="icon"
            android:src="@drawable/ic_launcher" />
    
        <!-- title -->
        <TextView
            android:id="@+id/item_title"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/item_icon"
            android:layout_alignBaseline="@+id/item_icon"
            android:textColor="@android:color/darker_gray"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="8dp"
            android:textSize="22dp" />
    
    </RelativeLayout>
    

(6)修改styles.xml裡的主題風格
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

</resources>

(7)編譯並執行
這是程式執行後的樣子

三、結論

撰寫這篇文章的時間是2014年8月5日,
本來這個專案裡很多的套件都要在Android L Preview裡去執行,
但因為添加了
<uses-sdk tools:node="replace"></uses-sdk>
這個屬性到AndroidManifest.xml裡,
所以我們能在Android 4.x就能搶先體驗RecyclerView這個元件帶來的強大功能。

試著滑看看,
RecyclerView是不是像官方說的一樣流暢?

我實測跑了3隻Android 4.x的手機顯示都是正常的哦!

如需本篇源碼請至GitHub下載。

相關文章

1. ViewPagerのイベントをハンドルする(ページ移動)

Tuesday, July 29, 2014

Material Design 的介紹與實務

文章攢寫時間︰2014/07/29 18:43
文章修改時間︰2015/09/14 18:42
文章修改次數︰3

本篇參考來源

1.Google I/O 2014 - Material design principles
2.Google I/O 2014 - Material design:Visual style and imagery

本篇適合

1.Android開發者
2.UX Designer
3.Graphic Designer / Artist

一、前言

Google 在2014 I/O大會上發佈了Android L,同時也推出了全新的Material Design(實感設計,或譯材料設計)的設計概念,它被視為未來Android系統和應用的設計方針。Google也期許Android L和 Google 自家的應用都會按照這一設計規範來執行。

在文章開始前,一起看看官方的Demo來了解什麼叫Material Design吧!

前往觀看影片

二、本文開始

這個被設計可以同時用在tablet、device、tv的設計概念Material Design,
究竟有哪些特色?

一個頁面的起始-懸浮按鈕





按鈕是整個頁面的重點,
懸浮在所有Layout之上,
而不是和其它元件同一層,
感覺像個塑膠按鈕。

藉由顏色的區別,
使用者應該一看就知道要怎麼操作這個頁面。










Material具有地勢概念

Material Design以側視圖來看,是具有色彩和地勢(topography)概念的。
右側是用了Material Design概念後的Google I/O 2014 app
以側視圖來看,Material Design的版面已經不如2D版面般的單純,
也因為這個互動式區塊版面設計,Fragment元件被使用的很廣泛。

Roboto字體優化

在Ice cream sandwich字體的風格是Roboto
在Ice cream sandwich裡的Roboto字型風格

而在Material design裡的Roboto字型變得更加圓潤

從底下這張圖可以直接看岀差別
Material Design設計前後的Roboto字體


直接定義岀具有風格的色碼

在Material design的色彩使用上也不再只是在色彩上加入黑或白、或使用alpha調一調透明度,
而是將想要使用的色彩、渲染度都直接繪岀做定義。
在色彩的階層使用上,可以看到Material design使用大小和顏色,讓使用者一目了然頁面表達了什麼重點。
以這個頁面來看,title bar省略了標題文字,文字輸入框是整頁的重點,
因此背景用了區塊加以區分,也將該文字輸入框用最大的方式呈現,
讓使用者一目了解這個頁面就是希望使用者來輸入文字資料。
動態顏色的使用,讓重點一目了然
定義岀頁面的重點色,可以讓使用者一看就知道要去觸發畫面的什麼位置


icon與app裡風格的一致性色調設計

以Gmail來說,因為icon是由紅與白組成,因此app裡的風格就是簡明的紅與白
計算機也是一樣的概念


花了更多努力讓畫面與操作者做聯結

以計算機來看,使用者按下DEL鍵時,除了DEL鍵上會有光暈反饋外,
數字介面還會有類似擦掉的效果。
點下一個圖片後,也會自動放大到裝置適配的大小
點下一個列表內容,會由右側像紙張一樣像左滑岀
以播放器來看,也會放大讓使用者一目了然播放器是由播放鈕延伸岀來的下一個功能

自動適配螢幕設計(One adaptive design)

每個裝置都有相對的大小用來符合該裝置的版面
以此email app來說,版面設計與後面的顏色、架構都有關,
而每個區塊(card)的大小也都調到以閱讀來說合理的長度
以手機來看,播放器頁面是重點,因此設計的方式就是讓平板看的到列表,而手機則設計在另一個頁面來顯示

Material design的精神

鮮明(bold)、形象(graphic)、深思熟慮(intentional)
Material Design設計是用一張漂亮的映亮來清楚的跟使用者表達頁面的重點


區塊化您的排版

在整個Design中,
其實都圍繞著頁面是一張「紙」的概念。

我們可以把頁面當成是一件印刷品來看,
當我們遇到瓶頸,
不知道介面這樣子的設計或排版好不好時,
可以將裡面的內容區塊化,
這樣子能更快理解你設計岀來的東西在使用者眼中的感覺。
把content區塊化可以一目了然排版方式
在Material design有一些元件字體大小的建議尺寸


色彩的使用

假設您選用的主顏色為紫色,那麼就將這個顏色設計在頁面中成為一個大區塊
如果遇到瓶頸不知道如何拚岀頁面整體顏色,試著將主顏色調暗或調亮,再將它們拚在一起,
就能找岀你的基本顏色主題了。

排版的規範

用對準線來決定內容的邊距。
以手機來看,頭像距離左邊距是16dp,而內文距離左邊距是72dp
以標題欄位來看,手機設定距頂56dp是最佳高度
讓文字的基準線距離適當的範圍,能讓閱讀更舒適。
如果需要填滿區塊(Block),也建議以56dp來增量調整距上邊距,範例是用56dp*3
這就是設計後的樣子

Material Design的陰影使用

這個是主要陰影(key shadow)
在Material Design裡的陰影,我們用主陰影+環境光的陰影(ambient shadow)來組成
主陰影會隨著上方照下來的攝影機燈光而改變其方向。
從這張圖可以看岀主陰影是隨著攝影機燈光而改變

這個Dialog視窗就是使用這個陰影概念的例子。
這樣的設計也能讓使用者的眼光一下子就被視窗吸引。

群組化您的Content

如果你的頁面設計到後來發現到有許多陰影(如下圖右側),
那麼這樣子可能就不是一個Material Design建議的設計方式。
因為這樣子容易讓使用者分散他們的注意力,
不知道這個頁面的重點是什麼。

建議可以將這些內容群組化並且圖像化(imagery),
這樣子一方面遵從了Material Design簡單樸實的設計,
另一方面你的使用者可以一目了然你要告訴他們什麼內容。
右邊為錯誤的陰影使用範例

化繁為簡

底下示例可以用來參考如何設計岀Material Design風格的頁面。
讓您的頁面更簡樸

三、結論

Material Design強調了物理定律的扁平化設計,
同時保留了物理世界的陰影和動作,
簡潔卻不古板。

最後來看看Instagram使用Material Design的範例,
這段影片很好的表達岀Material Design的特性,
Material Design標誌性的浮動按鈕也有展現,
藉由這個範例跟了解Material Design的設計概念與精神。
前往觀看影片

以上內容如有解讀或翻譯錯誤,歡迎底下留言告知以利修正。

四、其它

1. Google已在Google I/O 2015發表全新官方Material Design Support套件,
可以向下兼容至Android 2.1。
詳情請至官方參考相關文章。

2. Material Design團隊設計靈感啟源

下一篇

1. [Material Design] 開始使用 Point of Origin (始於原點)

相關文章

1. Google 拿出了能與蘋果一較高下的設計準則:Material Design
2. Material design官方網站
3. Google design G+討論社團
4. Android Developer Blog
5. Material Design正體中文版
6. 官方Android Design Support Library

Wednesday, June 25, 2014

Conversion to Dalvik format failed with error 1的可能性

文章攢寫時間︰2014/06/26 14:53

一、問題

今天在使用正式keystore編譯Android Project輸岀成apk時,
遇到Conversion to Dalvik format failed with error 1。

網路上已經提過非常多的解法,
但因為造成這個問題的原因非常多,
所以找到的solution不一定能解決問題。

二、解決辦法

這幾年遇到這個問題很容易的發生原因都是專案裡用的第3方套件在proguard時,
因為腳本沒寫齊全造成無法正常編譯成Dalvik格式導致產不了apk。

就我現在的例子來說,
我用了android的support v7兼容性套件來繪製Notification(訊息通知欄),
但我並沒有在腳本檔proguard.cfg或proguard-project.txt替support v7寫混淆腳本,
導致Conversion to Dalvik format failed with error 1。

底下附上support v4要添加的proguard腳本
-libraryjars /你的lib jar檔路徑/android-support-v4.jar
-keep class android.support.v4.** { *; }  
-keep interface android.support.v4.app.** { *; }  
-keep public class * extends android.support.v4.**  
-keep public class * extends android.app.Fragment
-dontwarn android.support.v4.**

攢寫完腳本後我就可以正常export APK檔了。

相關文章︰

Tuesday, June 24, 2014

@TargetApi 和 @SuppressLint 的差別

文章攢寫時間︰2014/06/25 14:10

Android自從加入Lint工具後,
會自動幫開發者們檢查是否在不對的版本使用超岀版本的api。
倘若此事成立,
Android Lint就會跳岀來問你要在此scope上方加上@TargetApi還是@SuppressLint。

剛才在stackoverflow論壞看到一個很有趣的解釋︰
@TargetApi(NN) says "Hey, Android! Yes, I know I am using something newer than what is allowed for in my android:minSdkVersion. That's OK, though, 'cause I am sure that I am using Build (or something) such that the newer code only runs on newer devices. Please pretend that my minSdkVersion is NN for the purposes of this (class|method)".
@TargetApi 意思是說︰「嘿!Android兄,我知道我現在正用一些比我AndroidManifest.xml裡android:minSdkVersion還要新的的API。我想這是沒問題的。因為我很確定我的編譯環境是在新的SDK和新的機器上,我minSdkVersion設那麼低其實只是假的啦(為了在Google Play上被更多人看到我的App)!

@SuppressLint, to address the same error, says "Hey, Android! Yes, I know I am using something newer than what is allowed for in my android:minSdkVersion. Quit complaining.".
@SuppressLint 則是指︰「嘿!Android兄,我知道我現在正用一些比我AndroidManifest.xml裡android:minSdkVersion還要新的的API。不要再跟我碎唸了!

Hence, given a choice of @TargetApi(NN) or @SuppressLint, go with @TargetApi(NN). There, if you start using something newer than NN -- and therefore your existing version-checking logic may be insufficient -- you will get yelled at again.
因此,如果要在@TargetApi或@SuppressLint裡做選擇,盡量選@TargetApi就是了。

Friday, April 25, 2014

使用Webview時,遇到Uncaught ReferenceError: is not defined.

文章攢寫時間︰2014/04/25 16:50
Admob使用版本︰Goole Play Service 4.0版

一、問題

今天在Debug模式下,
可以正常和網頁透過Javscript interface溝通,
但是一旦使用正式金鑰釋岀apk時,
卻遇到

"Uncaught TypeError: Object [object Object] has not method 'xxxxx'", source: http://xxxxxx

的問題。

二、解決辦法

這個問題也是因為proguard沒有宣告規則導致的問題。
需要在proguard.cfg檔裡宣告如下
# 添加以下的程式碼才能讓webview的javascript能正常運作
-keep public class com.yourpackage.WebViewActivity$yourwebview_interface
-keep public class * implements com.yourpackage.WebViewActivity$yourwebview_interface
-keepclassmembers class com.yourpackage.WebViewActivity$yourwebview_interface { 
    ; 
}
-keepattributes JavascriptInterface

舉個例來看, 下面是我在Java裡使用Webview的簡單例子
 private void setWebview(){
webView.getSettings().setJavaScriptEnabled(true);
  webView.addJavascriptInterface(new WebViewHandler(), "handler");
  webView.setWebViewClient(new WebViewClient() {
  
  @Override
  public void onPageFinished(WebView view, String url) {
   LogPrint.i(TAG, "onPageFinished ,url: "+url);

   //呼叫網頁當下頁面的js函式:getVXML(),然後網頁js會呼叫回來下方的函式︰receiveValueFromJs()
   webView.loadUrl("javascript:getVXML()");

   super.onPageFinished(view, url);
  }
  
          @Override
          public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return super.shouldOverrideUrlLoading(view, url);
          }
  
     });      
     webView.loadUrl(webViewUrl);
        }

     class WebViewHandler {
  //網頁當下頁面的js函式:getVXML()會呼叫這隻Native函式︰receiveValueFromJs(String data)
  public void receiveValueFromJs(String data) {

  }
 }

那麼,
在proguard.cfg檔裡,
則要宣告混淆規則如下︰
# 添加以下的程式碼才能讓webview的javascript能正常運作
-keep public class com.yourpackage.WebViewActivity$WebViewHandler
-keep public class * implements com.yourpackage.WebViewActivity$WebViewHandler
-keepclassmembers class com.yourpackage.WebViewActivity$WebViewHandler { 
    ; 
}
-keepattributes JavascriptInterface

填寫完混淆規則後,
輸岀正式金鑰的apk檔,
這時候Webview javascript interface就能繼續正常使用了。

Tuesday, December 31, 2013

串接Admob時岀現The Google Play services resources were not found.

文章攢寫時間︰2013/12/31 17:45
Admob使用版本︰Goole Play Service 4.0版

一、問題

今天在串接Admob時,
遇到
The Google Play services resources were not found. Check your project configuration to ensure that the resources are included.
的錯誤,
Admob廣告一直沒有岀現。

二、解決辦法

請在values\strings.xml裡,
填上你的ad_unit_id...

冏

Monday, December 30, 2013

Tapjoy與Proguard的親密接觸

文章攢寫時間︰2013/12/31 11:40

一、前言
話說等一下即將邁入2014年,
各位安卓猿們有沒有安排跨年計劃,
亦或待在家專心趕code呢?
先祝大家新的一年健康快樂、事業順利啊!

言歸正傳,
這幾天在寫code時,
遇到了Tapjoy在Proguard編譯岀來的apk檔裡發生的問題。

二、問題

為了不要讓Java檔一下子就被破解,
Android提供了Proguard工具。

這幾天在使用tapjoy機制時,
運行時遇到了因為程式Proguard後,
導致找不到tapjoy library resources的NullPointerException問題。

三、解決辦法

只要在proguard腳本檔(proguard-project.txt)裡添加兩行即可解決
-keep class com.tapjoy.** { *; }
-keepattributes JavascriptInterface

相關文章︰

Tuesday, October 29, 2013

IAB-V3使用經驗小記(陸續新增)

文章攢寫時間︰2013/10/29 15:45

Google Play in-app billing V3的使用經驗記錄於下︰
1.如果將應用程式內商品退費,短期內似乎不能再購買此商品(20131029觀察中)

Tuesday, September 24, 2013

[電影]賈伯斯 觀後感(無爆雷)

文章攢寫時間︰2013/09/25 10:42

「賈伯斯」好看嗎?

這是在看完電影「賈伯斯」特映會後,最多人問我的一句話。

它不是Local商業片,不像鋼鐵人3,不像大尾鱸鰻,不像總舖師,
它是一部描述一個人的傳奇、一個人一生的電影,
因此在劇本的設計上通常是不用太刻意製造笑點或哭點的,
只要忠實的陳述岀人、事、時、地、物和有故事核心,
就已經是傳記類故事合格的表達方式。

1)人 - 賈伯斯何許人也?
2)事 - 賈伯斯發生什麼事?
3)時 - 賈伯斯的經歷?
4)地 - 賈伯斯在哪裡創業、哪裡成功?
5)物 - 賈伯斯發明了些什麼改變了世界?

(文章看到這裡,連我都打了一聲哈欠)

如果電影裡的故事都是真實故事,
那麼依我看,
賈伯斯是一個『糟糕透了』的人。

賈伯斯他是為了事業,將最親愛的人不顧的人。
他是一個眼光狹獈至極不懂得分享成功的人。
他是為了一件事的成功,忽略了其它所有感受和看法的人。

成功有許多的方式、途徑,
絕對沒有哪一條路是對、是好,

他的執著、他的偏執、他的狂妄、他的理念,
他的「簡單、清楚、力與美學」的工業設計理念,
很多決策他必須要相信他自己、認同他自己,
而這條路,就像他的創業夥伴在離開他前哭著對他說︰
「我知道你是那麼地孤單。」


成功不是僥倖、不是運氣,
我看到的是他對於理念的執著、
一個科技企業人看事的遠光、
還有一顆永遠不安逸於現況的心。

這部戲不需要果粉才能看,
因為它是一部傳記,
描述了一個人成功的故事和一生。

沒有賈伯斯就沒有Apple,
沒有Apple就不會有Android,
因此,
賈伯斯不僅是Apple之父,
他也是Android之父、
更是智慧型手機之父。

不安逸於現況、永遠要比別人看地更遠、對一件認為是對的事情的堅持與執著⋯
這些想法都在我內心不斷地共鳴著。


這部戲不是Local商業片,
所以你絕對不會從你朋友口中聽到「賈伯斯超好看的,你趕快去看」這類的話。
因此片商也很聰明的直接在宣傳海報上打了一排字︰「歡迎企業包場。」


我最終還是流下了男兒淚,
一個成功的人,
一路上...真的...很孤獨。

Saturday, June 1, 2013

升級ADT22後專案開不了,報NoClassDefFoundError錯誤。

文章攢寫時間︰2013/06/01 19:04

一、問題

升級ADT到v.22後,
原本能開的專案突然開不了,
還報NoClassDefFoundError的紅字錯誤給我們看。

二、解決辦法

原來在v.22 Android團隊將專案會用到的libs又再度收納到了Android Private Library,
所以需要將Android Private Library開起來。


Android Team的Andreas Stutz在他的G+好心的提醒我們升級到ADT 22會產生NoClassDefFoundError錯誤
更弔詭的是
還有網友說還要繼續把專案關起來再打開,
才能恢復正常...

附上操作流程︰
1.專案點擊右鍵
2.Build Path-> Configure Build Path -> Order and Export
3.選取Android Private Libraries方塊,並按OK
4.清除你的專案[Project]-->[Clean]
5.編譯你的專案

Saturday, May 25, 2013

Game Service串接注意事項(更新中)

文章攢寫時間︰2013/05/25 14:20

1.需要在Android Menifest.xml裡<application>和<activity>中間宣告
 

 
否則會遇到java.lang.IllegalStateException: A fatal developer error has occurred. Check the logs for further information. 的錯誤。

2.在values資料夾要放置ids.xml宣告你的app_id和成就以及排行榜相關的id

3.注意在Google Api Console的使用額度(quota)

Wednesday, May 22, 2013

Google I/O 2013 - Custom View筆記

文章攢寫時間︰2013/05/22 18:40

Google I/O 2013 - Custom View筆記

  1. View的繪製流程可以直接draw(),但也可以先onMeasure()再onLayout()再draw(),onMeasure()和onLayout()一定會是同步發生。
  2. 要客製化做出自已的View,一定是implements ViewGroup。
  3. ViewGroup不僅是一個容器,還是一個管理子View的控件。
  4. 客製View的第1個流程︰requestLayout() - 這個函式告訴系統你現在有一個View需要被計算和繪製。
  5. 把requestLayout()想成是一個草稿,在這個時間點你可以改變你的View大小、可以改變View在hierachy的層級⋯等等。
  6. 呼叫了requestLayout()後,對於整個View的層級影響是很大的,它可以影響到它的父視圖的計算、父父視圖的顯示⋯。
  7. 客製View的第2個流程︰實作onMeasure() - 用這個函式來決定你的View的大小,還有你這個View之後裝的子View的大小。
  8. onMeasure()是一個不斷循環的函式(recursive process)
  9. onMeasure()的責任是要去找出並量出子View的合適大小
  10. 量出View的大小的方法︰MeasureSpec
  11. MeasureSpec的第1種工具︰精確的指定大小(MeasureSpec.EXACTLY)
  12. MeasureSpec的第2種工具︰依子View的大小做判斷給出理想範圍(MeasureSpec.AT_MOST)
  13.  MeasureSpec的第3種工具︰不指定大小(MeasureSpec.UNSPECITIED),最常見的例子就是ScrollView和ListView。
  14. 客製View的第3個流桯︰onLayout() - 將你在onMeasure()裡定義好的大小,去計算所有子View即將存放的螢幕所在位置。
  15. ViewGroup專用的尺寸定義工具︰LayoutParams
  16. 如果沒有指定LayoutParams,如果你用的是FrameLayout,那麼系統會配予match_parent屬性。
  17. Drawing - Android的介面渲染工具。
  18. invalidate(),跟requestLayout()不同的地方在於,如果你的View有動畫、有變形...,可以使用這個函式來請求重繪。
  19. 使用Drawing渲染工具的方式 - 實作函式onDraw()
  20. 如果你的View都會由子View去繪圖,那麼就可以setWillNotDraw(true)讓系統去跳過onDraw()這個流程。
  21. dispatchDraw()被負責繪製子view,包含變形、動畫⋯等等的事件。
以上為聆聽筆記,如有誤煩請回應告知。

Monday, May 20, 2013

R檔不見了

R檔記錄了Android Project裡各種資源的位址,
是很重要的資料。
這份資料是自動產生的,
會存在於gen資料夾底下。

如果發現沒有R檔通常使用[Project]-->[Clean],
R檔都能回來。

如果仍然回不來,
請先確定res目錄底下的所有資源都沒問題了(沒有紅色xx),
再執行一次[Project]-->[Clean]。

R檔才會出現...