2016年4月7日 星期四

Android Rajawali 3D 基礎設定與練習


一、在專案中的 gradle 中加入 Rajawali SDK 

  1. 在 build.gradle (Module : app) 內的 dependencies {...} 或在 Project Structure - Dependencies 中加入:
    compile 'org.rajawali3d:rajawali:1.0.186-SNAPSHOT@aar'

  2. 在 build.gradle (Project: yourProjectName) 內的 allprojects {repositories {...}} 中加入:
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }

二、建立 RajawaliRenderer 渲染器



  1. 在專案中加入繼承 RajawaliRenderer 的 Class
     public class mRender extends RajawaliRenderer {
        public mRender(Context context) {
            super(context);
        }
        @Override
        protected void initScene() {
        }
        @Override
        public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) {
        }
        @Override
        public void onTouchEvent(MotionEvent event) {
        }
    }
  2. mRender() 結構涵式中設定 FrameRate

    public Renderer(Context context) {
            super(context);
            this.context = context;
            setFrameRate(60);
     }
  3. 在 initScene() 中加入之後想要建立的物件 :
    Sphere - 球體物件、
    DirectionalLight - 定向光、
    DebugVisualizer - debug 隔線、
    Object3D - 3D 物件,可透過 LoaderOBJ、LoaderAWD、Loader3DSMax 等 Loader 來讀取模型資源檔、
    Camera 相機、
    ...

    而在 earthSphere 範例中所使用:

            /** 設定燈光 */
            directionalLight = new DirectionalLight(1f, .2f, -1.0f);
            directionalLight.setColor(1.0f, 1.0f, 1.0f);
            directionalLight.setPower(2);
            getCurrentScene().addLight(directionalLight);

            /** 設定材質 */
            Material material = new Material();
            material.enableLighting(true);
            material.setDiffuseMethod(new DiffuseMethod.Lambert());
            material.setColor(0);

            Texture earthTexture = new Texture("Earth", R.drawable.earthtruecolor_nasa_big);
            try{
                material.addTexture(earthTexture);

            } catch (ATexture.TextureException error){
                Log.d("DEBUG", "TEXTURE ERROR");
            }
           /** 設定地球球體 */
            earthSphere = new Sphere(1, 24, 24);
            earthSphere.setMaterial(material);
            getCurrentScene().addChild(earthSphere);

            getCurrentCamera().setZ(4.2f);

  4. 透過 Override - onRender() 方法,來更新渲染的物件。
    在 earthSphere 範例中所使用:
        @Override
        public void onRender(final long elapsedTime, final double deltaTime) {
            super.onRender(elapsedTime, deltaTime);
            earthSphere.rotate(Vector3.Axis.Y, 1.0);

        }



三、在 MainActivity 的 onCreate() 中加載 RajawaliSurfaceView 和設定 Surface 的 Render


在 earthSphere 範例中所使用:

    mRender renderer;

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

        final RajawaliSurfaceView surface = new RajawaliSurfaceView(this);
        surface.setFrameRate(60.0);
        surface.setRenderMode(IRajawaliSurface.RENDERMODE_WHEN_DIRTY);

        // Add mSurface to your root view
        addContentView(surface,
                               new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT));

        renderer = new mRender(this);
        surface.setSurfaceRenderer(renderer);
    }

完成後 Build And Run 在手機裝置上:

GitHub : NewSetRajawali

參考網址:


四、參考 RajawaliExamples 側選 General - ArcballCamera 範例建立 seekbar 控制練習

GitHub:MyRajawaliOpenGL




Android Vuforia AR 更換辨識物與顯示模型 (1)


既前篇 Android Vuforia AR 範例:Core Features 的開啟範例後,
我們要開始嘗試替換辨識物和顯示模型進行練習.

一、辨識物的設定

  1. 官網新增 Target,並下載檔案
    • 解壓縮得到 DemoDatabase.dat、DemoDatabase.xml 兩個檔案
  2. Assets 內放置檔案

  3. 修改至程式中(以 Core Features 項目中的 ImageTargets 進行)
    • 在 VuforiaSamples.ImageTargets 中修改程式部分:
      • -------------------------------------------------------------
      • 在 ImageTargets.java 中,修改和新增辨識物的 Database: DemoDatabase.xml
      • -------------------------------------------------------------
      • -------------------------------------------------------------
      • 在 ImageTargetRenderer.java 中,修改和新增辨識物資料庫的內容項目:TestTarget (可參考官網 TargetManager 或 DemoDatabase.xml)
      • -------------------------------------------------------------
      • DemoDatabase.xml 內容:
      • 在 ImageTargetRenderer.java 找到 renderFrame() 方法,並修改和新增 textureIndex 的判斷:
      •  renderFrame() 負責處理 AR 辨識結果和模型矩陣運算,之後將用於第三方模型套件應用.
    • 測試結果:

2016年3月31日 星期四

Android Vuforia AR 範例:Core Features


一、下載 SDK 與 Sample 和註冊會員

1. 至 Vufroia Developer Portal 下載 Vuforia 5.5 SDK 和 Samples - Core Features for Android 範例

  • https://developer.vuforia.com/
  • https://developer.vuforia.com/downloads/samples
2. 登入或註冊 Vuforia 帳號
3. 進入 Develop 分頁,裡面有兩項目:
  • License Manager:
    • 管理 License,一個 License 可以多款 app 使用,但掃描次數皆累積於共用的那一個 License.
    • License 分級:
      • Starter:屬於免費使用。可以建立 Target 庫和下載掃描,但在雲端掃描上有次數限制(每個月最高上限) 和 會出現 Vuforia 無水印.
      • Classic:一次型付費。無法雲端掃描,但可移除浮水印.
      • Cloud:月費型付費。可雲端掃描且可移除浮水印,但雲端掃描量(每個月最高上限)有收費分級制.

  • Target Manager:
    • 管理 Target,可以建立多個 Database 存放不同的辨識物,透過上傳辨識物可判斷該辨識物的可辨識度(星數越高越好辨識),辨識物可以放 Vuforia 端進行 Cloud Reco 判斷或是下載至裝置內存判斷.兩著差別在於 Cloud 有數量計數收費標準,而下載則不用.

License Manager

Target Manager


二、開啟和執行 Samples - Core Features 

  1. 解壓縮下載的 SDK 檔案後,可以看到裡面有的檔案資料夾路徑:
    • 參考 readme.txt 所描述的結構說明
    • vuforia-sdk-android-5-5-9 \
      • assets \                                            Additional assets required to use Vuforia SDK
        • frame-markers-transparent \
      • build \                                              Vuforia SDK
        • include \                                Commented header files
        • java \                                     Precompiled Java classes
          • vuforia \
            • Vuforia.jar
        • lib \                                        Dynamic link libraries
          • armeabi-v7a \
            • libVuforia.so
      • licenses \                                        License Agreements
        • license_3rdpartynotice.txt
      • samples \                                        Destination folder for sample applications
        • readme.txt                                              
  2.  Android Studio 開啟 VuforiaSample,並在專案中加入下列三項:

    1. Vuforia.jar:專案中建立 libs 資料夾,並將 .jar 放入其中和 Add Library
    2. libVuforia.so:專案中建立 jniLibs,並將 armeabi-v7a 資料夾和 libVuforia.so 加入
    3. license key:將官網 License Manager 中,所建立的 Key 複製並加入程式中.








  3. 在 SampleApplicationSession.java 的 InitVuforiaTask 中,找到 Vuforia.setInitParameters() 方法:
    Vuforia.setInitParameters(
            Activity activity,
            int flags,
            "your Key"
    )




  4. 完成以上步驟後,build and run 在自己的手機裝置上進行測試:
    在此以 ImageTargets 選單項目進行































Android NDK 環境安裝 for mac



1. 至官方網站下載 ndk,並解壓縮至自訂資料夾

官方網址:http://developer.android.com/intl/zh-tw/ndk/downloads/index.html
舉例路徑:Users/Peter/DevelopKit/NDK/



2.  打開 Terminal 命令终端,建立 bash_profile 


  • 一. 输入 pico .bash_profile 
  • 二. export NDK_ROOT=/Applications/Android-NDK/android-ndk-r10e 
  • 三. export PATH=$PATH:$NDK_ROOT 
  • 四. 最後儲存( control+X) 選 Y 



3. 設定 Android Studio 外部工具:Android Studio → Preferences... → External Tools


-------------------------
新增 NDK Build
-------------------------
Name: NDK Build
Group: NDK
Description: NDK Build
Options: 全打勾
Show in: 全打勾
Tools Settings:
Program: NDK目錄/ndk-build
Parameters: NDK_PROJECT_PATH=$ModuleFileDir$/build/intermediates/ndk NDK_LIBS_OUT=$ModuleFileDir$/src/main/jniLibs NDK_APPLICATION_MK=$ModuleFileDir$/src/main/jni/Application.mk APP_BUILD_SCRIPT=$ModuleFileDir$/src/main/jni/Android.mk V=1
Working directory: $SourcepathEntry$

-------------------------新增 Javah-------------------------
Name: Javah
Group: NDK
Description: Javah
Options: 全打勾
Show in: 全打勾
Tools Settings:
Program: /usr/bin/javah (用 which javah 查詢目錄)
Parameters: -v -jni -d $ModuleFileDir$/src/main/jni $FileClass$
Working directory: $SourcepathEntry$


4.建立 MyHelloJni 專案

  1. Android Studio -> New Project : MyHelloJni
  2. local.properties 增加:ndk.dir = NDK安裝目錄
  3. build.gradle 增加:ndk {moduleName "JniTest"}
  4. gradle.properties 增加:android.useDeprecatedNdk = true
  5. MainActivity.java 加入 LoadLibrary 和 Library 中的 c method :
  6. 右鍵新增 jni Folder ,並在裡面新增 Android.mk、Application.mk、JniTest.c 檔案
    ----------------------
    Android.mk
    ----------------------
    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE := JniTest
    LOCAL_SRC_FILES := JniTest.c

    include $(BUILD_SHARED_LIBRARY)

    ----------------------
    Application.mk
    ----------------------
    APP_ABI := all

    ----------------------
    JniTest.c
    ----------------------
    #include <jni.h>
    #include "project_peter_com_myhellojni_MainActivity.h"

    // JNICALL 呼叫自己專案的路徑方法,將 . 替換成 _ 
    JNIEXPORT jstring JNICALL Java_project_peter_com_myhellojni_MainActivity_getStringFromNative
            (JNIEnv *evn, jobject obj) {
        return (*evn)->NewStringUTF(evn,"Hello NDK demo");
    }

  7. 調用 javah 協助 JNI 相關檔案建置:對 MainActivityjava 右鍵 NDK (自定義 group) -> Javah.
    執行成功後,會在 jni 資料夾中自動產生 "路徑_ MainActivity.h"



  8. 使用 NDK Build 建置:對 JniTest.c 右鍵 NDK (自定義 group) -> NDK Build.
    執行成功後,會自動產生 jniLibs 資料以及各核心的 .so library


參考資料:

  • hAndroid筆記: Android Studio + NDK + JNI
    https://8085studio.wordpress.com/2015/04/25/android-studio-ndk-jni/
  •  Android 如何藉由JNI來使用C/C++程式?
  • https://magiclen.org/android-jni/

2015年12月2日 星期三

觸碰與手勢判斷

1. 判斷手勢滑動

public boolean onTouchEvent(MotionEvent event) {


        float X = event.getX(); // 觸控的 X 軸位置
        float Y = event.getY(); // 觸控的 Y 軸位置

        switch (event.getAction()) { // 判斷觸控的動作
        case MotionEvent.ACTION_DOWN: // 按下
            downX = event.getX();
            downY = event.getY();
            return true;

        case MotionEvent.ACTION_MOVE: // 拖曳
            return true;

        case MotionEvent.ACTION_UP: // 放開
            Log.d("onTouchEvent-ACTION_UP","UP");
            upX = event.getX();
            upY = event.getY();
            float x=Math.abs(upX-downX);
            float y=Math.abs(upY-downY);
            double z=Math.sqrt(x*x+y*y);
            int jiaodu=Math.round((float)(Math.asin(y/z)/Math.PI*180));//角度
             
            if (upY < downY && jiaodu>45) {//上
                Log.d("onTouchEvent-ACTION_UP","角度:"+jiaodu+", 動作:上");
            }else if(upY > downY && jiaodu>45) {//下
                Log.d("onTouchEvent-ACTION_UP","角度:"+jiaodu+", 動作:下");
            }else if(upX < downX && jiaodu< =45) {//左
                Log.d("onTouchEvent-ACTION_UP","角度:"+jiaodu+", 動作:左");
                // 原方向不是向右時,方向轉右
                if (mDirection != EAST) {
                    mNextDirection = WEST;
                }
            }else if(upX > downX && jiaodu< =45) {//右
                Log.d("onTouchEvent-ACTION_UP","角度:"+jiaodu+", 動作:右");
                // 原方向不是向左時,方向轉右
                if (mDirection ! = WEST) {
                    mNextDirection = EAST;
                }
            }
            return true;

            case MotionEvent.ACTION_POINTER_DOWN:  // 两只手指按下
              return true;
            case MotionEvent.ACTION_POINTER_UP: //一只手指离开屏幕,但还有一只手指在上面会触此事件
              return true;
        }
        return super.onTouchEvent(event);
}


2. 多点触控和缩放:配合 Bitmap、Matrix、TouchEvent 判斷,來完成圖片的縮放、旋轉、移動。


    /**计算两点之间的距离像素**/
    private float distance(MotionEvent e) {
                                                                                                                
        float eX = e.getX(1) - e.getX(0);  //后面的点坐标 - 前面点的坐标
        float eY = e.getY(1) - e.getY(0);
        return FloatMath.sqrt(eX * eX + eY * eY);
    }
                                                                                                            
    /**计算两点之间的中心点**/
    private PointF getMidPoint(MotionEvent event) {
                                                                                                                
        float x = (event.getX(1) - event.getX(0)) / 2;
        float y = (event.getY(1) - event.getY(0)) / 2;
        return new PointF(x,y);
    }

      
    //获得旋转角  
    public float getAngle(MotionEvent Event)  
    {  
        double DeltalX=Event.getX(0)-Event.getX(1);  
        double DeltalY=Event.getY(0)-Event.getY(1);  
        return (float)Math.atan2(DeltalX, DeltalY);  
    }  


Android中图片的多点触控和缩放 (imageView):http://smallwoniu.blog.51cto.com/3911954/1252191
Android开发学习之ImageView手势拖拽、缩放、旋转 (imageView):http://blog.csdn.net/qinyuanpei/article/details/17751481

模擬拖曳小圖示的 Android 程式 (surfaceView):http://dazi2012.blogspot.tw/2012/05/android.html?m=1

3. 應用於 activity 中的架構:
範例為新增自定義的 view,並在裡面繪製圓。透過觸碰改變圓的大小。

import ...
public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new TouchView(this));           
    }

    //自建的類別,繼承View,當成活動之畫面呈現
    private class TouchView extends View{

         public TouchView(Context context) {
              super(context);
         }
         @Override
         protected void onDraw(Canvas canvas) {
              super.onDraw(canvas);
         }
         //螢幕觸控事件處理,這邊是簡易方法只能偵測layout,無法針對特定元件進行監聽
         @Override
         public boolean onTouchEvent(MotionEvent event) {

                    switch (Event.getAction() & MotionEvent.ACTION_MASK){
                    case MotionEvent.ACTION_DOWN :
                            
                            break;
                    case MotionEvent.ACTION_POINTER_DOWN:

                            break;
                    case MotionEvent.ACTION_MOVE :
                            invalidate();//自動呼叫onDraw方法
                            break;
                    case MotionEvent.ACTION_POINTER_UP:
            
                            break;
                    case MotionEvent.ACTION_UP:

                            break;
                    default :
                            break;
                    }
                    return true;
         }
    }
}




參考資料:
  • Android:多點觸控之應用以及取得螢幕解析度 :http://chihweiwu.blogspot.tw/2012/08/android_30.html

常見環境問題

在Eclipse或Android Studio撰寫Android程式時常常會遇到以下幾個問題,這些也是StackOverflow上一直有人不斷提出的:
  1. 「R.java」是Android自動產生的資源檔(通常會在/gen目錄下),但怎麼在我一個動作後她就整個不見了呢?
    • 她沒有不見,是/res/layout(或其他的/res/xxx)下的檔案有錯誤讓她編不出來,所以只要先解決layout(或其他的/res/xxx)的錯誤後->點擊Project名稱->在「Project」功能表上再按一下「Clean」,R.java就恢復了喔!
  2. 加入一個library後出現了「Android Eclipse Jar mismatch! Fix your dependencies」錯誤,天啊!該怎麼辦?
    • 這種通常發生在專案有使用到library,而那個library剛好也有用到「android-support-v4.jar」,但問題就是library專案原本專案的「android-support-v4.jar」版本不同,造成的錯誤(Eclipse會自動做sha1來比對是否一致),但不一定是Android的support library,也有可能是其他衝突的jar包。這問題很好解決,如果是support library的話,在library專案原本專案的libs目錄下都找到「android-support-v4.jar」,按右鍵Delete掉,之後一併在library專案原本專案上按右鍵->Android Tools->Add Support Library,將最新的support library加入讓兩邊的include的library一致,就解決這個問題了。
  3. 錯誤的Include library專案方法
    • Java開發者可能會搞混把專案要用到的library以Java Build Path的方式include進來,請記得若是你要include的是一個library專案(如:actionbarsherlock或Facebook SDK等),應該要在想include library的專案上按右鍵->Properties->Android->在Library的區塊「Add」->把你要include的library專案加進去,這樣才是正確的方法。



Reference Website :
  • 幾個開發Android應用程式常遇到的錯誤與解法
  • http://www.dotblogs.com.tw/cheng/archive/2014/07/18/r-java-android-jar-mismatch-include-library.aspx

常用功能開發

-----------------------------------
1. 手機資料
-----------------------------------
 - 取SIM卡資料
透過getSystemService(TELEPHONY_SERVICE)取得 TelephonyManager物件,在透過其方法取得SIM卡資料(先生成Adapter,在從陣列撈)
取SIM卡狀態:getSimState() 回傳1-無sim卡,2-需PIN解鎖,3-需PUK解鎖,4-需Network PIN解鎖,5-已就緒,0-狀態未知
取SIM卡卡號:getSimSerialNumber()
取SIM卡供應商代碼:getSimOperator()
取SIM卡供應商名稱:getSimOperatorName()
取SIM卡國別:getSimCountryIso()

 - 取得電信及手機相關資訊
TelephonyManager及android.provider.Settings.System(一樣先丟陣列或物件,再取)
ex:telMgr = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
取得手機號碼:getLine1Number()
取得電信網路國別:getNetworkCountryIso()
取得電信公司名稱:getPhoneType()
取得網路類型:getNetworkType()
取得手機IMEI:getDeviceId()
取得IMEI SV:getDeviceSoftwareVersion()
取得IMSI:getSubscriberId()

 - 來電處理
onCallStateChanged

 - 開啟WiFI、GFS
getSystemService、WiFiManager.isWiFiEnabled()、LocationManager.isProviderEnabled()

 - 錄音
MediaRecorder、getExternalStorageState()、File.CreateTempFile、.setAudioSorece
setAudioSamplingRate()、setAudioEncodingBitRate()、setOutputFormat()、setAudioEncoder()

 - 控制音量
AudioManager、getStreamVolume(AudioiManager.STREAM_RING)、getRingerMode()
AudioManager.ADJUST_LOWER、AudioManager.ADJUST_RAISE、
AudioManager.RINGER_MODE_NORMAL、AudioManager.RINGER_MODE_SILENT、AudioManager.RINGER_MODE_VIBRATE


-----------------------------------
2. 基本
-----------------------------------
 - 切換Activity
Intent

 - 滑動的相片
Gallery

 - 樣式主題
Style

 - 取Drawable寬高
Bitmap、BitmapFactory.decodeResource()、getHeight()、getWidth();

 - 取得螢幕寬高
DisplayMetrics、getWindowManager().getDefaultDisplay().getMetrics()、widthPixels、heightPixels

 - TextView - Linkify
透過Linkify依不同類型的文字(電話、郵件、連結)而呼叫不同的相對應程式開啟。

 - 浮動訊息 
Toast
可透過LinearLayout放入圖片丟給Toast,toast.setView(layout)

 - 狀態列
NotificationManager、Notification、PendingIntent
可設定icon、tickerText(文字)、defaults(提醒效果)
提醒效果有:DEFAULT_SOUND(聲音)、DEFAULT_LIGHTS(螢幕發亮)、DEFAULT_VIBRATE(振動)、DEFAULT_ALL(三種都動作)

 - 桌面小工具(Widget)
AppWidgetProvider、BroadcastReceiver去listen、onUpdate()

 - 圖片透明度
Paint、setAlpha


-----------------------------------
3. 功能性
-----------------------------------
 - app載入前播放影片
Uri.parse()、VideoVeiw、setOnCompletionListener


 - 手勢滑動頁面效果
ViewFlipper,簡單來說就是透過觸控壓螢幕的位置X、Y滑動後始末座標去判斷位移,最後在呼叫動畫並切換到下個頁面。
onTouchEvent()、MotionEvent.ACTION_DOWN、MotionEvent.ACTION_UP、event.getX()、event.getX()、setInAnimation()、setOutAnimation()
另一方法:ViewGroup、Scroller、VelocityTracker


 - 多點觸控Multi-touch
onTouchEvent、MotionEvent.getAction()、MotionEvent.ACTION_DOWN(UP)、MotionEvent.ACTION_POINTER_N(1234..)_DOWN(UP)
getPointerCount()、findPointerIndex()、getX()、getY()


 - 換手機背景圖
setWallpaper()
取目前桌布:getWallpaper


 - 變更螢幕方向
setRequestedOrientation()、getRequestedOrientation、LANDSCAPE、PORTRAIT、onSensorChanged、mSensorListener、
透過方向感應onSensorChanged偵測變化在進行變更螢幕方向,另變化後使用onConfigurationChanged進行改變後進行的事件。


 - 定時啟動(鬧鐘、日曆提醒)
getSystemService(ALARM_SERVICE)、TimePickerDialog、AlarmManger、set、setRepeating、BroadcastReceiver


 - 開機執行
onReceive() 繼承BroadcastReceiver


 - 離開跳出訊息確認
AlertDialog.Builder() .setPositiveButton()  . setNegativeButton()
離開按鈕使用setOnClickListener去呼叫AleretDialog.Builder,並設定屬性若點選關閉或返回


 - 隱藏式側選單
SlidingDrawer.setOnDrawerOpenListener() 開啟、SldingDrawer.setOnDrawerCloseListener() 關閉、GridView


 - 解析RSS
SAXParser、SAXParserFactory、SAXParserFactory.newInstance()、newSAXParser()、getXMLReader()、MyHandler()
getParsedData()、getRssTitle()



參考資料: