2014年12月29日 星期一

[Android] No resource found that matches the given name '@style/Theme.AppCompat.Light' / 解決方法

剛開始安裝android / eclipse的時候通常都會遇到這個問題,

相信這個問題困擾許多人已久(包括我)...終於了解+找到解法以後把他寫起來以免下次又忘記!


首先,甚麼叫做IsLibrary?

這個選項打勾就代表說你把該專案當成library(所以不能亂勾,雖然勾了也沒太大影響(?))


Step1:
加入library(File->Import)



選擇sdk-extras-android-support-v7-appcompat資料夾,記得要勾選copy to workspace

Step2:

確認專案->右鍵Properties->anddroid(左側)->Library(isLibrary)是否勾選


Step3:

進到目標Project(就是有錯誤的那個project)

同樣也是properties->android->library->把錯誤的remove掉



add->剛剛新建立的android-support-v7-appcompat->OK (這邊請不要勾選is library)


Step4:

又發現錯誤!!



在builders這邊也有錯所以我們在Builders->Libraries->選擇錯誤的項目->edit->選圖片內的路徑

下面這個也有錯,所以也修正過來

Step5:

完成! 看到style.xml這邊完全沒有錯誤了,project也是沒有驚嘆號出現


就是這樣!幾個步驟做完就可以囉!

如果還是無法記得要去SDK Manager下載新的extras(要按install)

END


2014年12月23日 星期二

[Android] 如何修改Android內Icon

往往製作App的最後一個步驟就是要製作一個Icon,

製作Icon非難事,illustrator即可,甚至icon maker也可以輕意辦到。

但如何把修改好的(製作好的)Icon放進去Eclipse呢?

在專案中找到

1.AndroidManifest.xml

2.點開裡面->Icon->Browse...

3.create new icon..

製作好以後(一直next),選擇你製作好的icon即可。

重新build後,就可以拉!

End.

2014年11月28日 星期五

[Android] 寫入/讀取檔案(內部記憶體)

其實寫入或讀取內部/外部記憶體只差在一個要增加權限(androidmanifest.xml)一個不用而已,

所以如果你是要寫入sd卡,還是可以看一下這篇:)


寫入的部分:

//寫入檔案
private String filename = "MySampleFile.txt";
private String filepath = "MyFileStorage";
File myInternalFile;

.............以上為宣告...................

// 目前時間
Date date = new Date();
// 設定日期格式
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss");
// 進行轉換
String dateString = sdf.format(date);

try {
ContextWrapper contextWrapper = new ContextWrapper(getApplicationContext());
File directory = contextWrapper.getDir(filepath,MODE_APPEND);// 不存在就創建,存在就追加
myInternalFile = new File(directory, filename);
FileOutputStream fos = new FileOutputStream(myInternalFile,true);//要加上true才會是append否則預設false
String s = dateString + "," + event+"\n";// +事件
fos.write(s.getBytes());
fos.close();
Log.d("foutstream", "stream");
} catch (IOException e) {
e.printStackTrace();
}

看起來十分簡單,實際運作也十分簡單。

建立一個檔案,這邊記得要用MODE_APPEND(不存在就創建檔案,存在就繼續寫下去),如果用MODE_PRIVATE會蓋掉喔!

然後在FileOutputStream這邊預設是false(append false),因此要改成
FileOutputStream fos = new FileOutputStream(myInternalFile,true);
這樣才能成功複寫。



讀取的部分:

String myData = "";
try {
FileInputStream fis = new FileInputStream(myInternalFile);
DataInputStream in = new DataInputStream(fis);
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String strLine;
while ((strLine = br.readLine()) != null) {
myData = myData + strLine + "\n";
}
in.close();
                }catch(Exception e){}


就是用FileInputStream讀取,然後每行每行讀出來,讀到沒有為止,最後記得一樣要關stream!



十分的簡單,但也花了我不少時間,記錄一下。END


2014年11月19日 星期三

[Android] 如何讓TextView可以自動捲軸,當文字超過該範圍時,自動出現scrollbar?

最近寫到一個工具程式可以ping IP,不過因為有時候出現的文字太長了,textview沒辦法完全顯

示,就上網找了一下該如何使用scrollbar呢?


其實很簡單在xml檔的textview裡面增加兩行


        <TextView
            android:id="@+id/Send_txv"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:maxLines = "200"
            android:scrollbars = "vertical"          
            android:text=""
            android:textAppearance="?android:attr/textAppearanceSmall" />

第一行的數字可以修改maxLine就是最大顯示多少行,scrollbars則是是要橫幅還是直幅


還沒結束!

在程式碼的部分

Receiver = (TextView) findViewById(R.id.Receive_txv);
Receiver.setMovementMethod(new ScrollingMovementMethod());

並按Ctrl+Shift+O,import需要的東西,就完成囉!


2014年11月17日 星期一

[Android] TextWatcher EditText listener/ EditText的監聽器 & AlertDialog

看到別人都可以輸入完EditText1 就跳到EditText2,到底是如何做到的呢?

如果在主要的xml上面有的EditText 要做到這一部分,相信應該不難,網路上也有。

但如果你是要使用AlertDialog跳出一個視窗,該視窗可以輸入東西(EditText),同時又能輸入完跳

下一個的話,如何做呢?


********************************************
本篇是根據這篇教學: 小黑的android教室 來修改的
********************************************



一開始也是就直接複製過去,沒想到居然出現了nullpointer exception!

想了很久才想到原來是因為我的元件不是在activity_main.xml裡面,而一開始的設定

setContentView又是設定activity_main.xml,這就難怪他抓不到了(R.id.元件名稱)!

再解釋更清楚一點,就是當他跳出這個EditText的時候,卻找不到該元件id號,

NullPointerException的例外狀況馬上跳出來。

所以我們需要使用LayoutInflater.inflate這工具來使他(android)明白我們要用的

R.id.元件名是誰/在哪邊(在哪個xml)。在宣告的時候也要這樣用:

mEdit1 = (EditText) login_view.findViewById(R.id.Edit1);     才行


以下是程式碼


Step1;

在onCreate()這邊要初始化一個TextWatcher模組,讓他可以不停監聽

private void InitTextWatcher() {
// 建立文字監聽
TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}

@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// 如果字數達到3,取消自己焦點,下一個EditText取得焦點
if (mEdit1.getText().toString().length() == 3) {
mEdit1.clearFocus();
mEdit2.requestFocus();
}

if (mEdit2.getText().toString().length() == 3) {
mEdit2.clearFocus();
mEdit3.requestFocus();
}

if (mEdit3.getText().toString().length() == 3) {
mEdit3.clearFocus();
mEdit4.requestFocus();
}

// 如果字數達到3,取消自己焦點,隱藏虛擬鍵盤
if (mEdit4.getText().toString().length() == 3) {
mEdit4.clearFocus();
IBinder mIBinder = MainActivity.this.getCurrentFocus()
.getWindowToken();
InputMethodManager mInputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
mInputMethodManager.hideSoftInputFromWindow(mIBinder,
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
};

// 加入文字監聽
mEdit1.addTextChangedListener(mTextWatcher);
mEdit2.addTextChangedListener(mTextWatcher);
mEdit3.addTextChangedListener(mTextWatcher);
mEdit4.addTextChangedListener(mTextWatcher);
}

因為這邊是輸入IP,所以Length設定3。

Step2:

寫一個AlertDialog

public void InitPopup_IP_setting(){
//-----------取得Login Layout reference----------
     LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
     login_view = inflater.inflate(R.layout.ip_setting,null);
   
     //-----------產生登入視窗--------
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
     builder.setTitle("Connect");
     builder.setMessage("Input Target IP:");
     builder.setView(login_view);
     dialog = builder.create();
     dialog.show();
   
 mEdit1 = (EditText) login_view.findViewById(R.id.Edit1);
 mEdit2 = (EditText) login_view.findViewById(R.id.Edit2);
 mEdit3 = (EditText) login_view.findViewById(R.id.Edit3);
 mEdit4 = (EditText) login_view.findViewById(R.id.Edit4);    
     InitTextWatcher();    
}

**這邊有多一個xml檔就是ip_setting**

ip_setting.xml內容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/LinearLayout5"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/Edit1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="fill"
            android:layout_weight="1"
            android:digits="0123456789"
            android:ems="10"
            android:inputType="number"
            android:maxLength="3" >

            <requestFocus />
        </EditText>

        <TextView
            android:id="@+id/TextView05"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="." />

        <EditText
            android:id="@+id/Edit2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:digits="0123456789"
            android:ems="10"
            android:inputType="number"
            android:maxLength="3" />

        <TextView
            android:id="@+id/TextView06"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="." />

        <EditText
            android:id="@+id/Edit3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:digits="0123456789"
            android:ems="10"
            android:inputType="number"
            android:maxLength="3" />

        <TextView
            android:id="@+id/TextView04"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="." />

        <EditText
            android:id="@+id/Edit4"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:digits="0123456789"
            android:ems="10"
            android:inputType="number"
            android:maxLength="3" />
    </LinearLayout>

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="IP_xml_click"
        android:text="Enter" />

</LinearLayout>


好了,就這樣讓AlertDialog+TextWatche完美結合了!

2014年10月28日 星期二

[Android] Navigation Drawer的使用(簡易)

如果有看過Facebook app左側或右側可以拉出來的側邊欄, 那麼你已經看過Navigation Drawer了

不過在實際使用上不是那麼的容易(不然就不用寫教學了), 這邊我也研究了兩次,第一次失敗(覺

得太複雜就先做別的),第二次也就是這次才成功。決定把這個有點難的功能寫下來,當然也不

能說是全部都搞懂,就是把會的寫下來,以免以後忘掉。


這個是完成圖-也就是在手機上左側往右邊拉會出現這樣

的ListView選單。要多少選項可以自行調整(超過螢幕的

長度我就沒有測試了),選項一從編號0開始(所以用switch

case 搭配抓取該item的position就可以應用了)

















已經大概把Navigation Drawer大概介紹完了(詳細介紹可以找網路),先上程式碼:

(程式碼是下載 TonyCube來修改的)

Step1:

private void InitDrawer(){

fragmentManager = getFragmentManager();

layDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
lstDrawer = (ListView) findViewById(R.id.lst_drawer);

layDrawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);

mTitle = mDrawerTitle = getTitle();
drawerToggle = new ActionBarDrawerToggle(
this,
layDrawer,
R.drawable.ic_drawer,
R.string.drawer_open,
R.string.drawer_close) {

@Override
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
//getActionBar().setTitle(mTitle);
      //這邊可以改string內容,關閉時上方標題會改變,如果沒有要標題就不能用這一行
}

@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
//getActionBar().setTitle(R.string.drawer_open);
//這邊可以改string內容,開啟時上方標題會改變,如果沒有要標題就不能用這一行
}
};
drawerToggle.syncState();

layDrawer.setDrawerListener(drawerToggle);
}

首先一定是初始化,因此你需要宣告:

   private ActionBarDrawerToggle drawerToggle;
   private CharSequence mDrawerTitle;
   private CharSequence mTitle;
   private String[] drawer_menu;

以免等等會出現錯誤。

findByViewId的部分就是讓程式知道你要的那個元件是在xml裡面的哪一個,

setDrawerShadows的部分很明顯的就是設定陰影(這邊我沒有修改)

mTitle/Title的部分,如果你前面有設定隱藏title,那麼請把這一行+下面setTitle註解掉,因為會出錯

onDrawerOpen/onDrawerClose就是當側邊欄被拉開時/關閉時的動作,裡面可以自由發揮

setDrawerListener就是設一個監聽器,可以隨時抓取動作


Step2:

private void InitDrawerList(){
drawer_menu = this.getResources().getStringArray(R.array.drawer_menu);
//ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.drawer_list_item, drawer_menu);

List<HashMap<String,String>> lstData = new ArrayList<HashMap<String,String>>();
//多少個item@String Array => for跑幾次
for (int i = 0; i <drawer_menu.length; i++) {
HashMap<String, String> mapValue = new HashMap<String, String>();
//mapValue.put("icon", Integer.toString(R.drawable.ic_launcher));
              //要圖示的話才需要加這一行
mapValue.put("title", drawer_menu[i]);
lstData.add(mapValue);
}
SimpleAdapter adapter = new SimpleAdapter(this, lstData, R.layout.drawer_list_item2, new String[]{"icon", "title"}, new int[]{R.id.imgIcon, R.id.txtItem});
lstDrawer.setAdapter(adapter);

//側選單點選監聽器
lstDrawer.setOnItemClickListener(new DrawerItemClickListener());
}

這部分是在設定選項,你有多少個選項for就跑多少次(當然在res-value-Strings-StringArray裡面要

先設定好),旁邊圖示我沒有使用,當然要使用的話也是可以就把mapValue那一行重新恢復即

可。一樣設定監聽器,讓使用者點選時可以馬上反應。

Step3:

 private class DrawerItemClickListener implements ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }
    }
   
private void selectItem(int position) {
        Fragment fragment = null;
        FragmentTransaction fragmentTransaction;
        switch (position) {
case 0:
fragment = new Config();
//layDrawer.closeDrawer(lstDrawer);
info_txv.setVisibility(View.INVISIBLE);
main_txv.setVisibility(View.INVISIBLE);
imageView.setVisibility(View.INVISIBLE);
       fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Linear_left_drawer, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.commit();
break;

case 1:
InitPopup_IP_setting();

break;

case 2:
Self_Test();
break;

default:
//還沒製作的選項,fragment 是 null,直接返回
return;
}

        layDrawer.closeDrawer(lstDrawer);
    }

這部分監聽器會監聽使用者點選哪個item,會收到position是哪一個,然後再使用selectItem這個函

式處理,這邊使用switch...case。第一個選項是使用一個fragment去取代目前的fragment,由於

fragmentTransaction.addToBackStack(null)無法回去到原本的fragment,因此這個選項有回不去的

風險,請小心使用。第二個選項這邊是設定AlertDialog(),跟NavigationDrawer不相關,不重要因此

就不贅述,第三個選項亦同,default,或是任何你還沒設定的選項請使用return而不是break,否則就

會看到你的app掛點。最後記得要使用closeDrawer()來關閉NavigationDrawer。



NavigationDrawer大概就是這幾部分,然後如果要新增一個頁面(就是讓你點選一個選項會跳到

另一個頁面)的話,在res-layout這邊新增一個xml檔,在src裡面新增一個java檔*如下圖



這樣就可以連結囉!


以上就是NavigationDraw的小心得。 End


2014年10月17日 星期五

[Android] Sign & Unsign number (0~256) vs (-128~127)

*不一定是完全正確,可參考*

寫android寫到一個難題,解決了以後把他記下來,以免下次又忘記。

這個問題java也會遇到(其實android根本就是用java寫的....),所以java的也可以稍微參考一下

-----------------------------------------------------------------------------------------------------------------------

在android 裡面byte是沒辦法超過127的,所以如果我們要丟128以上的數字出去,就必須要作調整

不然即使強制轉換: (byte)129 這樣 ,仍然無法使用。java的世界裡面只有


0~127(正數)

---------------------------------

128~255(必須用負數表示)


在255的時候為 -1 因此.....128必須用-128表示(128-256 = -128)

也就是說 if( x > 127){
                      x = x-256;
                    }

這樣java才看得懂。

有看到網路上的人用&&FF ,不過那方法我也不太懂,所以就沒用了。

End.
               

2014年10月7日 星期二

[Android] 一些遇到的困難解決方式 跟 經驗談

android 寫到現在終於也快有一個作品出來了,

最常遇到的問題不外乎是import libary是哪一個(整個project掛掉) . R.id無法自動生成 . 明明沒

報錯結果實際run的時候卻出現nullpointer exception....

--------------------------------------------------------------------------------------------------------------------------

這些應該都是android新手常遇到的問題吧?!


  • Library的部分前幾篇有講到,要去properties把某個勾勾去掉




  • R.id無法自動生成 問題可能是出在xml檔有Error (API Level太低或太高,xml碼出錯....)

          解決完畢以後,去Project->clean 即可。同樣你新增一個物件,R.id找不到的時候也是可以

          用clean來處理。


  • 明明沒報錯卻有null pointer exception通常是沒有new,或是找不到id,等等.....
          這邊特別提一下如果你在"非"main thread想要setText或任何改變UI的行為,都會因為找不

          到你要修改的id而產生錯誤,當然即使你有了id number 還是不行,Android只允許在main 

          thread中修改UI部分。

  • 關於Thread / Handler 有些滿不錯的網站:
          Thread & Handler





  • 關於Tab / View這類的
         https://github.com/

         這上面有許多範例程式可以讓你下載回來修改


大概就是這樣,多寫多看囉! over

2014年9月24日 星期三

[Android] ViewPager 遇到在不同xml , 呼叫非setContentView內的R.id.物件 之解決方式

最近在改良Android的APP,先是view flipper,然後現在進階到view pager。

ViewPager可以作出很棒的特效,問題就是在xml方面,如果你要設定物件的話就必須要先

findViewbyId(),這乍聽之下沒甚麼問題,可是假如不是在main.xml檔(主要的xml)的物件呢?

我們使用findViewById()還是可以找到R.id.物件名稱,但只要你一使用的話就會出現錯誤-

java.lang.IllegalState...... NullPointerException....等等。為什麼會出現這樣的錯誤呢?

答案是因為你呼叫的id不在現在的頁面上(Android抓不到他)因此就出現nullpointerException!

在寫的時候也遇到這樣的問題,因此就上網查查看有沒有人也遇到這樣的問題?

發現這篇: 這裡

大意就是說

1.先宣告一個全域變數: private View currentViewPage

2.在 public class MyViewPagerAdapter extends PagerAdapter {} 裡面加入

  @Override
public void setPrimaryItem(ViewGroup container, int position,
Object object) {
currentViewPage = (View) object;
}

也就是把現在的position的object轉型成view然後放入currentViewPage(第一步驟設定的變數)裡

面。

3.最後   IP_edt = (EditText) currentViewPage.findViewById(R.id.IP_edt01);
      IP_edt.setText("123");

就可以用了喔!(當然IP_edt已經在前面有宣告過了)


希望可以幫助也同樣遇到困難的人!






2014年9月19日 星期五

2014年9月5日 星期五

[Android] 儀表板式顯示數值,類Gauge View / 使用Seek Circle 實作



This is a Tutorial about android - like Gauge View or Seek Circle, edit by civetcat.

if you need to do something like meter on UI / Speed meter or anything like that ,

you can just download the zip file,watch the code.

Download Link: SeekCircle-Thread-version  // SeekCircle-Normal-version

Original Code : https://github.com/Necat0r/SeekCircle
------------------------------------------------------------------------------------------------------------

以上為給國外朋友參考用。

正文開始:

  因為有需要,所以研究了一下如何在Android上面做出一個儀表板型的UI,一開始看了很多國

內的文章幾乎都沒有寫,只有大陸那邊網站稍稍有提到一些,但下載回來都是亂碼,好不容易找到

了一個國外的open-source,於是就下載回來自己修改。

  過程當中刪除了一些我覺得不是很必要的code,如果要的話也是可以去原網址這裡下載來

看看,使用方法為用手觸控來調整百分比(要滑動那個圓),看起來滿炫的!但我需要的不是用觸控

型的,所以這邊修改了一下改為你餵數值進去才會動。

第一個版本跟第二個版本其實沒有差很多,只是一個把餵數值的code另外獨立出來做一個thread

而已。


Code這邊可以分為三部分: MainActivity / SeekCircle / ProgressCircle


MainActivity當然就是主程式

/**
 * MainActivity.java [V 1.0.0]
 * classes :﹛com.caryfish.circleprogress.MainActivity
 * 剢豌  create at 2014-8-18 狟敁4:20:40
 */
package com.civetcat.circleprogress;

import com.caryfish.circleprogress.R;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {

public SeekCircle s_circle;
public static Handler handler;
public sending_thread send_thread;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

SeekCircle seekCircle = (SeekCircle) findViewById(R.id.seekCircle);// 建一個seekCircle物件,找id
Log.d("MainActivity", "start...");

try {
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
int[] receive_obj = (int[]) msg.obj; // object無法傳單一int
// 所以用int[]來傳送
updateText(receive_obj[0]);
break;
}
}
};
} catch (Exception e) {
Log.d("Handler exception", e.toString());
}

send_thread = new sending_thread();
send_thread.start();
// **這邊改用Thread.start() 讓他跑,updateText()則用handler去接收來更新資料

}

private void updateText(int input_progress) {
SeekCircle seekCircle = (SeekCircle) findViewById(R.id.seekCircle);
TextView textProgress = (TextView) findViewById(R.id.textProgress);

if (textProgress != null && seekCircle != null) {

seekCircle.setProgress(input_progress);
int progress = seekCircle.getProgress();// 會去ProgressCircle找getProgress()拿資料
textProgress.setText(Integer.toString(progress) + "%");
}
}
}

這邊因為要接收從另個thread來的數據,所以使用handler(請不要使用setText之類的,保證會有錯誤),updateText的部分則是會跑去setProgress(ProgressCircle.java)然後再把set好的數值用getProgress(同樣為ProgressCircle.java)取出來放在text裡面。

這部分應該是最簡單的部分,接下來兩個就是關鍵核心了!

雖然SeekCircle.java看似無用,但也是要他的建構子,所以不能把該檔案刪掉。

package com.civetcat.circleprogress;

import android.content.Context;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class SeekCircle extends ProgressCircle
{
/**
* A callback that notifies clients when the progress level has been
* changed. This includes changes that were initiated by the user through a
* touch gesture or arrow key/trackball as well as changes that were
* initiated programmatically.
*/

//SeekCircle建構子,勿刪
public SeekCircle(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
Log.d("SeekCircle","in Seek Circle constructor");
}

public SeekCircle(Context context, AttributeSet attrs)
{
super(context, attrs);
Log.d("SeekCircle","in Seek Circle constructor");
}

public SeekCircle(Context context)
{
super(context);
Log.d("SeekCircle","in Seek Circle constructor");
}
//SeekCircle建構子,勿刪 end
}


這邊能夠調整整個圖形介面的就屬ProgressCircle.java了(MainActivity也是拉...)

package com.civetcat.circleprogress;

import com.caryfish.circleprogress.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

//import com.seekcircle.library.R;

public class ProgressCircle extends View
{
private float mRingBias = 0.15f;
private float mSectionRatio = 5.0f;
private RectF mSectionRect = new RectF();
protected float mSectionHeight;

protected float mRadius;

protected int mMaxProgress;
protected int mProgress;

protected float mCenterX;
protected float mCenterY;

private Paint mPaint;
private int mColor1;
private int mColor2;
private int mInactiveColor;

{
mMaxProgress = 100;
mProgress = 0;

mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);

mColor1 = Color.parseColor("#ffff5900"); //頭端color = 紅
mColor2 = Color.parseColor("#ff33b5e5"); //尾端color = 藍
mInactiveColor = Color.parseColor("#ff404040");

mPaint.setColor(mColor1); // Set default
}
//------建構子
public ProgressCircle(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);

initAttributes(context, attrs);
}

public ProgressCircle(Context context, AttributeSet attrs)
{
super(context, attrs);

initAttributes(context, attrs);
}

public ProgressCircle(Context context)
{
super(context);
}

//建構子----end

private void initAttributes(Context context, AttributeSet attrs)
{
TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SeekCircle, 0, 0);
try
{
// Read and clamp max
int max = attributes.getInteger(R.styleable.SeekCircle_max, 100);
mMaxProgress = Math.max(max, 1);

// Read and clamp progress
int progress = attributes.getInteger(R.styleable.SeekCircle_progress, 0);
mProgress = Math.max(Math.min(progress, mMaxProgress), 0);
}
finally
{
attributes.recycle();
}
}

private void updateDimensions(int width, int height)
{
// Update center position
mCenterX = width / 2.0f;
mCenterY = height / 2.0f;

// Find shortest dimension
int diameter = Math.min(width, height);

float outerRadius = diameter / 2;
float sectionHeight = outerRadius * mRingBias;
float sectionWidth = sectionHeight / mSectionRatio;

mRadius = outerRadius - sectionHeight / 2;
mSectionRect.set(-sectionWidth / 2, -sectionHeight / 2, sectionWidth / 2, sectionHeight / 2);
mSectionHeight = sectionHeight;
}

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);

if (width > height)
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
else
super.onMeasure(widthMeasureSpec, widthMeasureSpec);

updateDimensions(getWidth(), getHeight());
}

@Override //改變size大小
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);

updateDimensions(w, h);
}

@Override
protected void onDraw(Canvas canvas)
{
// Center our canvas
canvas.translate(mCenterX, mCenterY);//偏移原本原點多少x,多少y ex:100,100 -> 101,101

float rotation = 360.0f / (float) mMaxProgress; //rotation 旋轉 360/100
for (int i = 0; i < mMaxProgress; ++i)
{
canvas.save();

canvas.rotate((float) i * rotation);
canvas.translate(0, -mRadius);

if (i < mProgress)
{
float bias = (float) i / (float) (mMaxProgress - 1);
int color = interpolateColor(mColor1, mColor2, bias);//漸層顏色
mPaint.setColor(color);//塗上顏色,在這一段
}
else
{
canvas.scale(0.7f, 0.7f);
mPaint.setColor(mInactiveColor);
}

canvas.drawRect(mSectionRect, mPaint);
canvas.restore();
}

super.onDraw(canvas);
}

private float interpolate(float a, float b, float bias) //interpolate = 插值,bias = 偏移,偏差
{
return (a + ((b - a) * bias));
}

private int interpolateColor(int colorA, int colorB, float bias)
{
float[] hsvColorA = new float[3];
Color.colorToHSV(colorA, hsvColorA);

float[] hsvColorB = new float[3];
Color.colorToHSV(colorB, hsvColorB);

hsvColorB[0] = interpolate(hsvColorA[0], hsvColorB[0], bias);
hsvColorB[1] = interpolate(hsvColorA[1], hsvColorB[1], bias);
hsvColorB[2] = interpolate(hsvColorA[2], hsvColorB[2], bias);

// NOTE For some reason the method HSVToColor fail in edit mode. Just use the start color for now
if (isInEditMode())
return colorA;

return Color.HSVToColor(hsvColorB);
}

/**
* Get max progress
*
* @return Max progress
*/
public float getMax()
{
return mMaxProgress;
}

/**
* Set max progress
*
* @param max
*/
public void setMax(int max)
{
int newMax = Math.max(max, 1);
if (newMax != mMaxProgress)
mMaxProgress = newMax;

updateProgress(mProgress);
invalidate();//重新繪製View
}

/**
* Get Progress
*
* @return progress
*/
public int getProgress()
{
Log.d("ProgressCircle","getProgress");
Log.d("ProgressCircle on getProgress,mProgress = ",Integer.toString(mProgress));
return mProgress;
}

/**
* Set progress
*
* @param progress
*/
public void setProgress(int progress)
{
Log.d("ProgressCircle","setProgress");
updateProgress(progress);//設定progress
}

/**
* Update progress internally. Clamp it to a valid range and invalidate the view if necessary
*
* @param progress
* @return true if progress was changed and the view needs an update
*/
protected boolean updateProgress(int progress)
{
Log.d("ProgressCircle","update Progress");
Log.d("ProgressCircle Update Progress,progress = ",Integer.toString(progress));
Log.d("ProgressCircle Update Progress,mProgress = ",Integer.toString(mProgress));
// Clamp progress , clamp = 抓
progress = Math.max(0, Math.min(mMaxProgress, progress));//先跟最大比 如果>100 就取100,再跟0比,最小到0 也就是0~100之間

if (progress != mProgress) //跟目前狀態(mProgress)不同的話
{
mProgress = progress;
invalidate();//重新繪製View
Log.d("ProgressCircle Update Progress,progress = ",Integer.toString(progress));
Log.d("ProgressCircle Update Progress,mProgress = ",Integer.toString(mProgress));
return true;
}

return false;
}
}


原始資料顏色部份是對調的,但我因為讓電池100%時看起來是綠色的,0%時為紅色的,所以就把

數據對調了一下。

值得特別注意的是  progress = Math.max(0, Math.min(mMaxProgress, progress));
                                  //先跟最大比 如果>100 就取100,再跟0比,最小到0 也就是0~100之間

這邊如果要調整最大最小值,可以透過這一行+mMaxProgress來調整。

然後invalidate();則是強制重新繪製view。

最後一塊則是Thread的部分,但thread的部分之前有寫過了,所以就不多加說明了。

其他的部分能研究的都在程式碼裡面有寫註解了,祝大家都可以順利寫出來!

附上載點



2014年9月3日 星期三

[Android] " no resorce found that matches the given name '' 解決方法


  1. 先使用SDK manager download 
  2. Extras -> Android Support Library
  3. 接著在File->import-> android-sdk\extras\android\support\v7(也就是剛剛下載的) -> 選appcompat
  4. 在目標的project上面點選右鍵->Properties->左邊有一欄->選Android->右下方Library->add->加入appcompat
  5. 完成。
簡單記錄一下

2014年9月2日 星期二

[Android] 如何開啟後隱藏Activty UI ?

在寫App的過程當中遇到兩個問題

1. 開機時自動啟動
2. 開啟後隱藏UI (用service在背景執行)

第一個問題可能之後幾篇會講到,不過大概就是android 會廣播一則開機訊息,你只要去抓那個訊

息就知道開機了,然後利用intent去啟動class...

第二個問題網路上比較少講到,這邊也沒有做到很完全


大概歸納為三個解決方案:

1.修改Theme 改為Theme.No_Display,但這經過實測,不行。

2.使用finish()函式,經過實測,可行。

使用方法:  在onCreate()裡面都做完事情以後新增一行 finish();

3.使用 moveTaskToBack()

public boolean moveTaskToBack (boolean nonRoot)

Added in API level 1
Move the task containing this activity to the back of the activity stack. The activity's order within the task is unchanged.
Parameters
nonRootIf false then this only works if the activity is the root of a task; if true it will work for any activity in a task.
Returns
  • If the task was moved (or it was already at the back) true is returned, else false.

Google API上面的形容,會回傳值true就是已經移到背景,false則否。

有人會使用moveTaskToBack(true),也有人說也可以改成false,但經過實際測試,兩者皆可。

但API上寫是說true是無論如何都會work,false只有activity在task的root的時候才會作用 。


2014年9月1日 星期一

[Andorid] Service 範例-背景執行/無UI

要做一個Service要先了解,service無法自己啟動,必須要靠startService()才能啟動。

同樣的啟動後也必須要使用stopService()關閉。

在這邊需要注意的有幾點

1) startService()

2) stopService()

3) AndroidManiFest.xml增加service權限


第一點為開啟service必要條件,當然你要new一個intent

所以就是:

Intent intent = new Intent(MainActivity.this,service_class.class);
startService(intent);

MainActivity為你目前的class,service_class則是另外開的java檔(那邊則是寫service做的事情)


第二點跟第一點很類似,只有差在start / stop的差別

Intent intent = new Intent(MainActivity.this,service_class.class);
stopService(intent);

第三點就是你需要在Android ManiFest.xml裡面新增權限

否則跑起來會有

"

Unable to start service Intent: not found

"
這樣的錯誤訊息出現

好了,總結上面 MainActivity該如何寫?

package com.example.service_nonui_demo;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;

public class MainActivity extends ActionBarActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(MainActivity.this,service_class.class);
startService(intent);
}
@Override
public void onPause(){
super.onPause();
Intent intent = new Intent(MainActivity.this,service_class.class);
stopService(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, 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);
}

/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {

public PlaceholderFragment() {
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}

}

Service部分

package com.example.service_nonui_demo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;

public class service_class extends Service{

@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
}
 

@Override
public void onStart(Intent intent, int startId) {
Toast.makeText(this, "Service start", Toast.LENGTH_SHORT).show();
}
public void onDestroy(){
super.onDestroy();
Toast.makeText(this, "Service stop", Toast.LENGTH_SHORT).show();
}
}

(記得要extends Service!)

Android ManiFest.xml部分

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.service_nonui_demo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.service_nonui_demo.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
            <service android:name="service_class"></service>
    </application>

</manifest>

記得要新增紅色字的這一行

如此一來,service就可以啟動了!

service在onStart()的地方開始運作,所以如果要做任何動作,可以寫在裡面喔!

範例只是做一個toast(跳出訊息)而已。

附上範例檔案 **點這裡下載**

End

2014年8月19日 星期二

[Android] 開機自動啟動APP/ 疑難雜症解答

在Android 系統裡面,要讓你的app在開機時候就啟動必須有兩個要件

1)BroadCastReceiver--這個東西是用來接收,android手機開機時會丟出的訊息(android.intent.action.BOOT_COMPLETED)

2)開權限--沒有路,怎麼走過去呢?
要在AndroidManiFest.xml增加權限,才能夠接收BOOT_COMPLETED

首先,先建立一支java class(在同一個類別下喔!)
像這樣

package com.example.thread_example;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BootAct extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Intent boot = new Intent(context,MainActivity.class);
                boot.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(boot);
}

}

這邊需要extends BroadcastReceiver 然後我們要改寫onReceive的內容,記得new Intent裡面是你要開啟的class名稱喔(這邊容易搞錯),然後使用startActivity來啟動。

權限的部分則是



綠色部分為所需要添加的程式碼,紅色部分則是要特別注意---名稱不要搞錯,像我是設為BootAct,搞錯就會出現Permission Denial: starting Intent .....這樣的錯誤。


OK,重新開機吧! 相信他重開之後就會開啟你的APP了。