分類
Jenkins

git plugin error on Jenkins : Permission denied (publickey).

Description :
安裝 git plugin 之後, 建立專案->組態->原始碼管理 -> Git -> Repository URL 並設定以下內容

ssh://username@xxx.xxx.xxx.xxx:port/absolute_path.git

 
Error :
此 error message 出現在 Repository URL下方

Failed to connect to repository : Command "/usr/bin/git -c core.askpass=true ls-remote -h ssh://username@xxx.xxx.xxx.xxx:port/absolute_path.git HEAD" returned status code 128:
stdout:
stderr: Permission denied (publickey).
fatal: The remote end hung up unexpectedly

 
Solution :
Step 1.
首先讓另一台主機(s2)使用ssh成功登入到jenkins主機上,並在jenkins主機的~/.ssh/authorized_keys 加入s2的id_rsa.pub
 
Step 2.
複製s2的id_rsa檔案貼到jenkins主機的家目錄任意資料夾中(e.g. /home/username/server/id_rsa_files/id_rsa )此rsa檔案將被使用在Step 3.
 
Step 3.
在jenkins主機安裝 Credentials plugin 並使用 Credentials plugin 建立一個 Credentials 內容如下
Kind : SSH Username with private key,
Scope : Global
Username : (任意)
Description :  (任意)
Private key :  From a file on Jenkins master (選擇Step 2.複製到jenkins主機上的s2 id_rsa檔(/home/username/server/id_rsa_files/id_rsa))
 
Step 4.
在原始碼管理 -> Git -> Repository URL 下方的 Credentials 內容設定為Step 3. 新增的Credentials
 
Step 5.
編輯 /etc/ssh/sshd_config 加入新的port e.g.

# Package generated configuration file
# See the sshd_config(5) manpage for details
# What ports, IPs and protocols we listen for
Port 22
Port 23
...

Port 23 即是新的Port,會使用在ssh://username@xxx.xxx.xxx.xxx:port/absolute_path.git 的 port
 
Step 6.
將Step 5.新增的Port number 設定到Repository URL中

ssh://username@xxx.xxx.xxx.xxx:23/absolute_path.git

完成後Error訊息就會消失
 
 
Note :
1.Permission denied的原因有很多, 首先從git repo建立者和jenkins使用者關係找起, 將2者config設定一致, 也在jenkins使用者中加入ssh public key, 無效
2.設定jenkins->設定系統-> Git plugin -> Global Config user.name Value 和 Global Config user.email Value 設定與git repo建立者相同, 無效
3.使用Credentials plugin建立jenkins主機的credential(類似Step 3. 但其中的Private key設定為jenkins本機的id_rsa)並設定在 原始碼管理 -> Git -> Repository URL 中 也是無效
4.在Repository URL的Port Number設定也有關係, 若不依照 Step 5.建立新Port, Permission denied還是無解

分類
Design Pattern

使用Simple Factory Pattern替換多建構式

Simple Factory Pattern:

提供產生物件的方法, 而無須關注實作內容, 其物件類型可為interface, abstract class, class

 
當類別內有多建構式存在且每個建構式無法清楚的說明其用途時, 可使用Simple Factory代替建構式, 在SimpleFactory中以清楚的方法名稱說明建構式的用途
這是我在專案遇到的情況, 我必須寫出1個Dialog, 此Dialog的元件在不同的情況下會有不同的組合, 如在A情況此dialog只有1個按鈕, 在B情況會有2個按鈕
一開始考慮使用建構式來決定產生何種dialog

package com.foxx.simplefactory;
import android.app.Dialog;
import android.content.Context;
import android.widget.Button;
import android.widget.TextView;
public class CustomDialog extends Dialog
{
    private String mDescription;
    private TextView mUpTextView;
    private TextView mDownTextView;
    private Button mUpButton;
    private Button mDownButton;
    /**
     * Used in one button
     */
    public CustomDialog(Context context, Button upButton) {
        super(context);
    }
    /**
     * Used in two button
     */
    public CustomDialog(Context context, Button upButton, Button downButton) {
        super(context);
    }
    /**
     * Used in one button and one description
     */
    public CustomDialog(Context context, Button upButton, String description) {
        super(context);
    }
}

 
CustomDialog的使用者除了從建構式的參數以及註解外, 無法了解建構式的使用方式(意圖), 為了解決這個情況我試著引入Simple Factory來修改

package com.foxx.simplefactory;
import android.content.Context;
import android.widget.Button;
public class CustomDialogFactory
{
    public static CustomDialog createOneButtonDialog(Context context, Button upButton)
    {
        return new CustomDialog(context, upButton);
    }
    public static CustomDialog createTwoButtonDialog(Context context, Button upButton,
            Button downButton)
    {
        return new CustomDialog(context, upButton, downButton);
    }
    public static CustomDialog createOneButtonOneDescriptionDialog(Context context,
            Button upButton, String description)
    {
        return new CustomDialog(context, upButton, description);
    }
}

 
使用者可根據Simple Factory的方法名稱選擇建立CustomDialog物件

package com.foxx.simplefactory;
import com.example.simplefactory.R;
import com.foxx.factorymethod.*;
public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CustomDialog oneButtonDialog = CustomDialogFactory.createOneButtonDialog(context, upButton);
        CustomDialog twoButtonDialog = CustomDialogFactory.createTwoButtonDialog(context, upButton, downButton);
        CustomDialog oneButtonOneDescriptionDialog =
        CustomDialogFactory.createOneButtonOneDescriptionDialog(context, upButton, description);
    }
}

 

Class Diagram

simplefactory_classdiagram
 

分類
Uncategorized

How to run Shell Script in Eclipse

Description :

Use eclipse to run shell script file
 

Step :

Open eclipse ->Run -> External tools -> External Tools Configurations… -> select Program ->
click “new launch configuration” -> select your .sh file at “Location” -> select your .sh file root directoy at “Working Directory” ->
click “Apply” -> click “Run” -> now you can see result at eclipse console

分類
CloneZilla

使用 CloneZilla 複製 HDD(大容量1TG) 到 SSD(小容量240G) on ubuntu

Target

使用 CloneZilla 複製 HDD(大容量1TG) 到 SSD(小容量240G) on ubuntu
 

System Requirements

ubuntu 12.04
clonezilla 20130703-raring-i386
Gparted
 

Description

HDD(總容量1TG)是原運作硬碟, 磁碟內容分為3個分割區
根目錄(/)(分割空間200G, 使用空間20G), 家目錄(/home)(分割空間780G, 使用空間120G), swap空間20G
而SSD(總容量240G)是用來替換原運作硬碟, 無任何空間分割
 

Error Handling

當來源碟(HDD 1TG)容量大於目的碟(SSD 240G), 複製時會出現目的碟容量不足錯誤訊息
這時候就需要一些trick way來避開, 首先必須滿足條件是HDD的”分割空間”必須小於SSD的總空間
如在Description提到HDD總分割空間為 200G(根目錄) + 780G(家目錄) = 980G 是大於 SSD 的分割空間(240G), 會造成目的碟容量不足
待會在Step 2.將HDD的分割空間縮小, 使得HDD總分割空間小於SSD
 

Setup

Step 1.
先對原運作硬碟HDD備份吧, 為了避免對原運作硬碟造成影響, 最好能夠準備一顆和原運作硬碟總容量相同大小的測試用硬碟, 再把原運作硬碟完全複製到測試用硬碟,
之後的動作全部使用測試用硬碟來執行
 
Step 2.
使用Gparted對HDD縮減分割空間, 以HDD的根目錄為例, 雖然分割空間有200G, 但使用空間只有20G, 所以可將根目錄的分割空間縮到20G, 讓分割空間和使用空間幾乎相同
對HDD的家目錄作相同的動作, 縮減分割空間到120G
Note :  在本步驟就是調整HDD的分割空間使其小於SSD的總空間
 
Step 3.
使用Gparted對SSD作分割空間的動作, 先在SSD切割一塊空間(大於20G即可), 此空間就是為了能完全複製HDD的根目錄
再切割另一塊空間(大於120G), 為了複製HDD的家目錄, 注意以上空間切割時可順便作貼齊的動作, 檔案格式選擇ext4
 
Step 4.
先複製HDD根目錄到SSD上吧, 使用CloneZilla選擇
device – device 處理整顆硬碟或部份分割區的直接複製 -> 專家模式 -> 複製本機分割區到本機分割區 ->
選擇來源分割區(注意此來源分割區必須選擇HDD的根目錄) -> 選擇目的分割區(注意此目的分割區必須選擇SSD的第一塊分割空間(大於20G)) ->
設定的進階參數(勾選”略去檢查目的碟空間是否夠大”) -> 略去檢查與修正來源分割區 -> -k 不要在目的碟的開機磁區產生分割區的分割表 ->
完成
Note : 完成後即可使用SSD嘗試開機, 可正常開機但無法登入, 因為HDD的家目錄還未複製到SSD上
 
Step 5.
再複製HDD的家目錄到SSD上, 使用CloneZilla選擇
device – device 處理整顆硬碟或部份分割區的直接複製 -> 專家模式 -> 複製本機分割區到本機分割區 ->
選擇來源分割區(注意此來源分割區必須選擇HDD的家目錄) -> 選擇目的分割區(注意此目的分割區必須選擇SSD的第二塊分割空間(大於120G)) ->
設定的進階參數(勾選”略去檢查目的碟空間是否夠大”) -> 略去檢查與修正來源分割區 -> -k 不要在目的碟的開機磁區產生分割區的分割表 ->
完成
Note 1. 完成後即可使用SSD嘗試開機, 可正常開機且登入, 所有的檔案都和HDD相同
Note 2. 完成後記得再去啟動 SSD 的 TRIM 功能
 

Review

事實上完成複製的條件在於先調整HDD分割空間使其小於SSD容量, 以避開容量不足的錯誤訊息, 只要能夠達到這點
無論是HDD -> SSD, SSD -> HDD 都能複製, 在windows系統也可成功複製

分類
Eclipse error

Eclipse error : Eclipse hangs at the Android SDK Content Loader(0%)

Description : Launch eclipse ide andstucked, “Android SDK Content Loader (0%)” show at of left-bottom dialog
 
Root Cause : Unknow
 
Solution :

cd workspace/.metadata/.plugins/org.eclipse.core.resources/.projects
rm -rf *

ps : Projects won’t be lost but Some projects will confuse need to rebuild
 
Note :
Solution1. Remove ~/.android/cache and ~/.android/ddms.cfg not worked for me !!!
Solution2. Eclipse -> Project -> Build Automatically -> uncheck this option -> restart eclipse fail !!!

分類
Uncategorized

Run "About" sample on Alljoyn for linux version

參考官網步驟
以下為紀錄成功過程

Export path

1. Open terminal and move to root dir
Note: In my case, root dir name is alljoyn-14.12.00a-src
 
2. Terminal type

export AJ_ROOT=`pwd`

 
 
3. Terminal type

export TARGET_CPU=x86

Note: <TARGET CPU> can be either x86_64, x86, or whatever value you set for “CPU=” when running SCons.
value of TARGET CPU must matchas path, e.g.
/alljoyn-14.12.00a-src/build/linux/x86_64/  or  /alljoyn-14.12.00a-src/build/linux/x86/
 
4. Terminal type

export LD_LIBRARY_PATH=$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/lib:$LD_LIBRARY_PATH

 
 

Run the AboutService Sample App

1. In root of dir(alljoyn-14.12.00a-src), type

$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/bin/samples/AboutService

Note: some message will print on terminal e.g.

BusAttachment started.
   0.037 ****** ERROR NETWORK                   common/os/posix/Socket.cc:346 | Binding (sockfd = 59) to 0.0.0.0 9955: 98 - Address already in use: ER_OS_ERROR
BusAttachment connect succeeded. BusName :AVq3OlHG.2
AboutObj Announce Succeeded.
   0.041 ****** ERROR NETWORK                   common/os/posix/Socket.cc:346 | Binding (sockfd = 58) to 0.0.0.0 9955: 98 - Address already in use: ER_OS_ERROR
   1.863 ****** ERROR NETWORK iodisp2_0         common/os/posix/Socket.cc:463 | Shutdown socket (sockfd = 11): 107 - Transport endpoint is not connected: ER_OS_ERROR

it will show message when client connects.
 

Run the AboutClient Sample App

1. In root of dir(alljoyn-14.12.00a-src), type

$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/bin/samples/AboutClient

Note: terminal print message e.g.

BusAttachment started.
BusAttachment connect succeeded.
WhoImplements called.
*********************************************************************************
Announce signal discovered
	From bus :R8gNQJgr.2
	About version 1
	SessionPort 900
	ObjectDescription:
		/example/path
			com.example.about.feature.interface.sample
	AboutData:
		Key: AppId	01 b3 ba 14 1e 82 11 e4 86 51 d1 56 1d 5d 46 b0
		Key: DefaultLanguage	en
		Key: DeviceId	93c06771-c725-48c2-b1ff-6a2a59d445b8
		Key: ModelNumber	123456
		Key: AppName	Application
		Key: DeviceName	My Device Name
		Key: Manufacturer	Manufacturer
*********************************************************************************
...
Calling /example/path/com.example.about.feature.interface.sample
Echo method reply: ECHO Echo echo...

 
2. AboutServer will print message e.g.

...
Session Joined SessionId = 3156666061
Echo method called: ECHO Echo echo...

 

分類
Uncategorized

Build AllJoyn framework for linux version

參考官網提供基本流程
以下是紀錄成功的流程
 

1. Setup

1-1. Build tools and libs

sudo apt-get install build-essential libgtk2.0-dev libssl-dev xsltproc ia32-libs libxml2-dev

 
 
1-2. Install Python v2.6/2.7 (Python v3.0 is not compatible and will cause errors)

sudo apt-get install python

Note:測試python 2.6.5, 2.7.3皆可, 若已安裝過則省略
 
1-3. Install SCons v2.0

sudo apt-get install scons

 
1-4. OpenSSL

sudo apt-get install libssl-dev

Note:似乎和Build tools and libs 有重複
 
1-5. Download the AllJoyn Source zip and extract source. The tree should look like below. Note, extra directories may exist.

root-source-dir/
  core/
      alljoyn/
      ajtcl/
  services/
      base/
      base_tcl/

Note: download source zip 選擇alljoyn-14.12.00a-src.tar.gz 解壓縮完會產生 alljoyn-14.12.00a-src 目錄,目錄內容如下

alljoyn_c     alljoyn_java  alljoyn_objc   build_core  common        README.md   SConstruct
alljoyn_core  alljoyn_js    alljoyn_unity  build.xml   manifest.txt  README.txt  services

除了選擇alljoyn-14.12.00a-src.tar.gz 以外還可使用git clone的方式來下載 source code 參考這裡, 但未測試

2. Build Sample

2-1. 開啟終端機移動到 alljoyn-14.12.00a-src 目錄中並輸入

scons BINDINGS=cpp WS=off BT=off ICE=off

terminal會出現一連串build step, 如下

scons: Reading SConscript files ...
Building bindings: cpp
Building services:
BULLSEYE_BIN not specified
GTEST_DIR not specified skipping common unit test build
BULLSEYE_BIN not specified
GTEST_DIR not specified skipping About Service unit test build
GTEST_DIR not specified skipping alljoyn_core unit test build
scons: warning: Ignoring missing SConscript 'build/linux/x86_64/debug/obj/services/about/SConscript'
File "/home/rcserver/projects/jellyfish/alljoyn-14.12.00a-src/alljoyn_core/samples/eventaction/SConscript", line 35, in <module>
scons: done reading SConscript files.
scons: Building targets ...
Install file: "alljoyn_core/docs/README.linux.txt" as "build/linux/x86_64/debug/dist/README.txt"
...

Note: 完成後會產生build資料夾, 後續 run sample 動作都與此有關
 
最後輸入

SERVICES="about,notification,controlpanel,config,onboarding,sample_apps"

 
 

分類
Uncategorized

AlertDialog + MultiChoiceItems

1.MainActivity.java

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity
{
    private Button mShowAlertDialog;
    private TextView mShowSelectedItemsOfAlertDialog;
    private String[] mItemsOfAlertDialog;
    private boolean[] mSelectedItems;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mItemsOfAlertDialog = getResources().getStringArray(R.array.dialog_items);
        mSelectedItems = new boolean[mItemsOfAlertDialog.length];
        mShowAlertDialog = (Button) findViewById(R.id.show_alert_dialog);
        mShowSelectedItemsOfAlertDialog = (TextView) findViewById(R.id.show_selected_items);
        mShowAlertDialog.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                showAlertDialog();
            }
        });
    }
    private void showAlertDialog()
    {
        Resources resources = getResources();
        AlertDialog.Builder ab = new AlertDialog.Builder(this);
        ab.setTitle(resources.getString(R.string.alert_dialog_name));
        ab.setMultiChoiceItems(R.array.dialog_items, mSelectedItems,
                new MultiChoiceClickListener());
        ab.setPositiveButton(resources.getString(R.string.ok),
                new ClickListenerForPositiveButton());
        ab.setNegativeButton(resources.getString(R.string.exit),
                new ClickListenerForNegativeButton());
        ab.show();
    }
    private final class ClickListenerForNegativeButton implements OnClickListener
    {
        @Override
        public void onClick(DialogInterface dialog, int which)
        {
            dialog.dismiss();
        }
    }
    private final class ClickListenerForPositiveButton implements OnClickListener
    {
        @Override
        public void onClick(DialogInterface dialog, int which)
        {
            String selectedItems = "";
            for (int i = 0; i < mSelectedItems.length; ++i) {
                if (mSelectedItems[i]) {
                    selectedItems = selectedItems + mItemsOfAlertDialog[i] + "\n";
                }
            }
            mShowSelectedItemsOfAlertDialog.setText(getResources().getString(
                    R.string.selected_items)
                    + "\n" + selectedItems);
            dialog.dismiss();
        }
    }
    private final class MultiChoiceClickListener implements OnMultiChoiceClickListener
    {
        @Override
        public void onClick(DialogInterface dialog, int which, boolean isChecked)
        {
            mSelectedItems[which] = isChecked;
        }
    }
}

Note:
showAlertDialog(), 其中setMultiChoiceItems()就是設定為Multi choice的重點, 第一個參數mItemsOfAlertDialog為soft coding帶入dialog item, dialog item定義在xml  中 如下
/res/values/arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array
        name="dialog_items">
        <item>item1</item>
        <item>item2</item>
        <item>item3</item>
        <item>item4</item>
        <item>item5</item>
    </string-array>
</resources>

第2個參數 mSelectedItems 則是代表dialog item是否被選中
MultiChoiceClickListener類別則為監聽alertdialog中item被選中的事件,其實比較方便的寫法是直接建立匿名類別,但如此一來showAlertDialog()長度會被大幅增加, 可讀性降低
ClickListenerForNegativeButton類別則為監聽alertdialog中Negative Button被點擊的事件, 可以看到事件啟動之後僅把dialog dismiss,沒有作其他事情
ClickListenerForPositiveButton類別則為監聽alertdialog中Positive Button被點擊的事件, 事件啟動後檢查mSelectedItems的element,如果是true代表在dialog是被勾選的
 
2.main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/show_alert_dialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/show_alert_dialog" />
    <TextView
        android:id="@+id/show_selected_items"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/selected_items" />
</LinearLayout>

 

分類
Uncategorized

Spinner + ArrayAdapter

Spinner 加上 ArrayAdapter 的基本使用
1.MainActivity.java

import java.util.Arrays;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
public class MainActivity extends Activity
{
    private Spinner mSpinner;
    private String[] mSpinnerItems = new String[] {
            "item1", "item2", "item3"
    };
    private TextView mShowSelectedItemInSpinner;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initLayout();
    }
    private void initLayout()
    {
        mShowSelectedItemInSpinner = (TextView) findViewById(R.id.selected_item);
        initSpinner();
    }
    private void initSpinner()
    {
        mSpinner = (Spinner) findViewById(R.id.exam_spinner);
        ArrayAdapter adapter = createArrayAdapterByStringArrayXml();
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mSpinner.setAdapter(adapter);
        mSpinner.setOnItemSelectedListener(new ItemSelectedListener());
    }
    private void showSelectedItem()
    {
        Object selectItem = mSpinner.getSelectedItem().toString();
        for (String item : mSpinnerItems) {
            if (selectItem.equals(item)) {
                mShowSelectedItemInSpinner.setText(item);
                break;
            }
        }
    }
    private ArrayAdapter<String> getArrayAdapterByStringArray()
    {
        return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mSpinnerItems);
    }
    private ArrayAdapter<String> getArrayAdapterByStringArrayXml()
    {
        String[] items = getResources().getStringArray(R.array.spinner_items);
        return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, items);
    }
    private ArrayAdapter<CharSequence> createArrayAdapterByStringArrayXml()
    {
        return ArrayAdapter.createFromResource(this, R.array.spinner_items,
                android.R.layout.simple_spinner_item);
    }
    private ArrayAdapter<String> getArrayAdapterByList()
    {
        List<String> itemList = Arrays.asList(mSpinnerItems);
        return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, itemList);
    }
    private final class ItemSelectedListener implements OnItemSelectedListener
    {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
        {
            showSelectedItem();
        }
        @Override
        public void onNothingSelected(AdapterView<?> parent)
        {
        }
    }
}

Note:
注意各種不同取得ArrayAdapter的方式
getArrayAdapterByStringArray() 直接使用Hard Coding帶入Spinner item, android.R.layout.simple_spinner_item為 android 預設的 spinner layout
getArrayAdapterByStringArrayXml() 使用Soft Coding帶入Spinner itme, 其中item定義在R.array.spinner_items中
createArrayAdapterByStringArrayXml() 也是使用Soft Coding帶入Spinner itme
getArrayAdapterByList() 也可以使用 List 帶入 spinner item
這裡只列出常用的幾種,其他請參考官網
setDropDownViewResource() 用來設定點擊Spinner後出現的下拉式選單
ItemSelectedListener類別則是用來監聽Spinner選擇的Item
 
2./res/values/arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array
        name="spinner_items">
        <item>item1</item>
        <item>item2</item>
        <item>item3</item>
    </string-array>
</resources>

Note:
使用Soft Coding定義Spinner Items
 
3.activity_main.xml

<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" >
    <Spinner
        android:id="@+id/exam_spinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/selected_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/exam_spinner"
        android:layout_marginLeft="50dp"
        android:layout_marginTop="20dp"/>
</RelativeLayout>
分類
Uncategorized

Customize style for checkbox

1.
首先建立 Selector(customize_checkbox_selector.xml),以套用到checkbox上

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/press"
        android:state_checked="true" android:state_focused="true"/>
    <item android:drawable="@drawable/press"
        android:state_checked="false" android:state_focused="true"/>
    <item android:drawable="@drawable/press" android:state_checked="true"/>
    <item android:drawable="@drawable/normal" android:state_checked="false"/>
</selector>

注意:
press的圖片就是點擊checkbox顯示的圖片, normal的圖片就是未點擊checkbox的圖片
把customize_checkbox_selector.xml放到res/drawable/中
 
2.
建立Style, 和 Button 不同,checkbox必須另外建立一個style,並在style中引入第一步驟建立的Selector, 最後才在checkbox的xml中套用style

<style name="customize_checkbox_style"
        parent="@android:style/Widget.CompoundButton.CheckBox">
        <item name="android:button">@drawable/customize_checkbox_selector</item>
</style>

 
3.
在checkbox xml中套用

<CheckBox
            android:id="@+id/test_checkbox"
            style="@style/customize_checkbox_style"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/customize_bg"
            android:text="@string/teststring">
</CheckBox>

注意: 若checkbox的文字已經有設定了但在畫面上還是看不出來,請小心checkbox的字體顏色是否和背景顏色相同