2019年3月6日 星期三

Android 產生Signed Apk時錯誤 Keystore was tampered with, or password was incorrect

在產生signed apk時

明明輸入正確密碼卻出現 Keystore was tampered with, or password was incorrect 錯誤訊息

密碼打到懷疑人生...一直以為是密碼錯誤

 於是開始找密碼  

方法一: https://github.com/nanqiu/android-keystore-password-recover/blob/master/README.md

下載這個程式 把你的xxxx.jks放到同一資料夾

建立一個關鍵字wordlist.txt

然後想想可能的關鍵字 打在裡面

打開 cmd下指令
java -jar AndroidKeystoreBrute_v1.05.jar -m 3 -k xxxx.jks -d "wordlist.txt" 

 找到密碼

結果密碼沒錯啊

回到Android Studio再產生看看, 一樣是說密碼錯誤 wtf, 難道是其中有一個密碼錯誤

於是再繼續找密碼


https://stackoverflow.com/questions/28034899/how-to-retrieve-key-alias-and-key-password-for-signed-apk-in-android-studiomigr

這篇提到如果有用過Android Studio 2.2之前的

Android Studio會有Log檔案, 裡面竟然有密碼啊啊啊啊啊

Windows系統位置在
C:\Users\xxxxx\.AndroidStudio2.2\system\log
裡面有個 idea.log.1
用記事本打開他, 搜尋一下password,  you got it !!!
兩個密碼都有
結果都正確啊啊啊啊啊啊

於是我開始懷疑根本是Android Studio的問題

https://stackoverflow.com/questions/32987530/android-studio-keystore-was-tampered-with-or-password-was-incorrect
提到換密碼就好

https://stackoverflow.com/questions/2889238/keystore-change-passwords

先找到keytool位置
C:\Program Files\Java\jdk1.8.0_73\bin
然後將你的xxxx.jks放在這
cmd下指令
 
keytool -storepasswd -keystore xxxx.jks
會要你輸入舊密碼與新密碼
 
keytool -keypasswd  -alias xxxx -keystore xxxx.jks
alias名稱要正確
然後一樣輸入舊密碼與新密碼
完成


回到Android Studio, 一定要先Clean Project
再Generate Signed Bundle/APK

成功產生!!! 害我嚇到吃手手 WTF

2018年8月2日 星期四

iOS Swift 信用卡 Credit Card

使用套件
Braintree可以快速使用
但是目前還是測試階段的樣子
 
基本流程就是 
1.先取得Token(自己的server 這裡用官方測試提供的)
2.再將Token丟給braintree server
3.取得Nonce再丟給自己的server註冊
4.然後自己server再跟braintree server溝通

這邊提供iOS手機端方法

Podfile加入 
pod 'BraintreeDropIn'
下載套件完成後在storyboard拉個有按鈕的UI
Ex:
Braintree有提供測試用的信用卡號
可來這裡參考 
https://developers.braintreepayments.com/reference/general/testing/ruby
 
ViewController.swift

import UIKit
import BraintreeDropIn
import Braintree

class ViewController: UIViewController {

    @IBOutlet weak var btnShow: UIButton!
    @IBOutlet weak var indicator: UIActivityIndicatorView!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func fetchClientToken() {
        self.btnShow.isEnabled = false
        self.indicator.isHidden = false
        // TODO: Switch this URL to your own authenticated API
        // 測試用官方取得Token Url
        let clientTokenURL = NSURL(string: "https://braintree-sample-merchant.herokuapp.com/client_token")!
        let clientTokenRequest = NSMutableURLRequest(url: clientTokenURL as URL)
        clientTokenRequest.setValue("text/plain", forHTTPHeaderField: "Accept")
        
        URLSession.shared.dataTask(with: clientTokenRequest as URLRequest) { (data, response, error) -> Void in
            // TODO: Handle errors
            let clientToken = String(data: data!, encoding: String.Encoding.utf8)
            print("clientToken:\(clientToken!)")
            self.showDropIn(clientTokenOrTokenizationKey: clientToken!)
            
            // As an example, you may wish to present Drop-in at this point.
            // Continue to the next section to learn more...
            }.resume()
    }

    @IBAction func jumpDropIn(_ sender: UIButton) {
        fetchClientToken()
    }
    
    
    func showDropIn(clientTokenOrTokenizationKey: String) {
        DispatchQueue.main.async {
        let request =  BTDropInRequest()
        let dropIn = BTDropInController(authorization: clientTokenOrTokenizationKey, request: request) { (controller, result, error) in
            if (error != nil) {
                print("ERROR:\(error.debugDescription)")
            } else if (result?.isCancelled == true) {
                print("CANCELLED")
            } else if let result = result {
                // Use the BTDropInResult properties to update your UI
                print("result paymentOptionType:\(result.paymentOptionType)")
                // 取得nonce後,拿nonce去後端註冊
                print("result paymentMethod.nonce:\(result.paymentMethod!.nonce)")
                print("result paymentIcon:\(result.paymentIcon)")
                print("result paymentDescription:\(result.paymentDescription)")
            }
            
                self.btnShow.isEnabled = true
                self.indicator.isHidden = true
            controller.dismiss(animated: true, completion: nil)
        }
        self.present(dropIn!, animated: true, completion: nil)
        }
    }
}





2017年8月21日 星期一

iOS Swift Multiple Language Localizable 切換多語系

不是認手機語系, 而是直接在app上切換語系

1.在Main.storyboard 拉三個按鈕與一個Label


2.與ViewController.swift建立連接
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var labMessage: UILabel!
    

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func btnEnglish(_ sender: UIButton) {
        UserDefaults.standard.set("Base", forKey: "lang") //儲存語系為基本
        setMessage()
    }
    
    @IBAction func btnJapan(_ sender: UIButton) {
        UserDefaults.standard.set("ja", forKey: "lang") //儲存語系為日文
        setMessage()
    }
    
    @IBAction func btnChinses(_ sender: UIButton) {
        UserDefaults.standard.set("zh-Hant", forKey: "lang") //儲存語系為繁體中文
        setMessage()
    }
    
    func setMessage() {
        labMessage.text = "A Song of Ice and Fire".localized //使用擴展功能 localized
    }

}

extension String{

    var localized: String {
                
        let lang = UserDefaults.standard.string(forKey: "lang")
        
        let bundle = Bundle(path: Bundle.main.path(forResource: lang, ofType: "lproj")!)

        return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
        
    }
}

3.建立Localizable.strings檔案
New File... -> 找到 String File  -> 輸入Localizable.strings -> Create
建立完成後點選檔案->右邊的 ->再點選Localize...

4.回到PROJECTInfo 點選Localizations的 + 會出現所有語言, 這裡選擇Japanese(ja)與Chinses(zh-Hant), 括弧內為設定文字對應的縮寫

勾選建立的Localizable.strings就好

 接著Localizable.strings會出現三個檔案

分別在 Localizable.strings(Base)輸入
"A Song of Ice and Fire" = "A Song of Ice and Fire";

Localizable.strings(Japanese)輸入
"A Song of Ice and Fire" = "氷と炎の歌";

Localizable.strings(Chinese(Traditional))輸入
"A Song of Ice and Fire" = "冰與火之歌"; 





檔案下載:
https://github.com/terryyamg/MultipleLanguageTest
參考連結:
https://stackoverflow.com/questions/25081757/whats-nslocalizedstring-equivalent-in-swift
https://medium.com/lean-localization/ios-localization-tutorial-938231f9f881

2017年8月10日 星期四

iOS Swift Cache Image 簡易的Image Cache

簡單的Cache Image, 讓圖片不用一直下載,造成卡卡的問題

1.建立一個Project, 先在Info.plist ->右鍵 Open AS -> 選Source Code
在dict內加入,
NSAppTransportSecurity
NSAllowsArbitraryLoads 

2.接著建立MyTableViewCell.swift, 後面在Cell建立Image View後拉過來對應
class MyTableViewCell: UITableViewCell {

    @IBOutlet weak var iv: UIImageView!
    
    var ivUrl: NSURL! //圖片來源url
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

3.然後在Main.storyboard拉一個Table View

在cell裡面加個Image View, 對應Class: MyTableViewCell
Table View拉一下dataSource與delegate到ViewController
記得給Cell的Identifier,這裡寫ImageCell

4.建立一個CustomImageCache.swift檔案
import UIKit

class CustomImageCache {
    
    static let sharedCache: NSCache = { () -> NSCache<AnyObject, AnyObject> in
        let cache = NSCache<AnyObject, AnyObject>()
        cache.name = "MyImageCache"
        cache.countLimit = 20 // Max 20 images in memory.
        cache.totalCostLimit = 10*1024*1024 // Max 10MB used.
        return cache
    }()
    
}

extension NSURL {
    
    typealias ImageCacheCompletion = (UIImage) -> Void
    
    
    var cachedImage: UIImage? {
        return CustomImageCache.sharedCache.object(
            forKey: absoluteString as AnyObject) as? UIImage
    }
    
    func fetchImage(completion: @escaping ImageCacheCompletion) {
        // 如果需要客製化取得資料在此做
        let task = URLSession.shared.dataTask(with: self as URL) {
            data, response, error in
            if error == nil {
                if let data = data, let image = UIImage(data: data) {
                    CustomImageCache.sharedCache.setObject(
                        image,
                        forKey: self.absoluteString as AnyObject,
                        cost: data.count)
                    DispatchQueue.main.async() {
                        completion(image)
                    }
                }
            }
        }
        task.resume()
    }
    
}

5.最後在ViewController.swift
import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var url = [String]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 重複50張圖片
        url = Array(repeating: "http://g03.a.alicdn.com/kf/HTB1hvNUIFXXXXbBXFXXq6xXFXXX2/3D-Diamond-Embroidery-Paintings-Rhinestone-Pasted-diy-Diamond-painting-cross-Stitch-font-b-coffe-b-font.jpg", count: 50)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return url.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell: MyTableViewCell = tableView.dequeueReusableCell(withIdentifier: "ImageCell") as! MyTableViewCell
        let ivUrl = NSURL(string: url[indexPath.row])
    
        cell.ivUrl = ivUrl // For recycled cells' late image loads.
        if let image = ivUrl?.cachedImage { //抓過了 -> 直接顯示
            cell.iv.image = image
            cell.iv.alpha = 1
        } else { //沒抓過 ->下載圖片
            cell.iv.alpha = 0
            // 下載圖片
            ivUrl?.fetchImage { image in
                // Check the cell hasn't recycled while loading.
                if cell.ivUrl == ivUrl {
                    cell.iv.image = image
                    UIView.animate(withDuration: 0.3) {
                        cell.iv.alpha = 1
                    }
                }
            }
        }
        
        return cell
    }
}



檔案下載:
https://github.com/terryyamg/CacheImag
參考來源:
http://www.splinter.com.au/2015/09/24/swift-image-cache/

2017年7月1日 星期六

iOS Swift Custom View Xib 客製化 視窗

建立一個上下左右跑進來的視窗,使用xib客製化元件

1.建立一個xib檔案 File -> New -> File ... 選擇View
2.MyView.xib


a.建立 MyView.xib 並拉一個Label到視窗
b.選擇View
c.設定屬性Size:Freeform 其他None

3.建立MyView.Swift
//
//  MyView.swift
//  JumpViewTest
//
//  Created by Terry Yang on 2017/6/30.
//  Copyright © 2017年 terryyamg. All rights reserved.
//

import UIKit

class MyView: UIView {
    
    @IBOutlet weak var labCustom: UILabel!
    
    var view:UIView!
    
    // Label Inspectable
    @IBInspectable
    var mytitleLabelText: String? {
        get {
            return labCustom.text
        }
        set(mytitleLabelText) {
            labCustom.text = mytitleLabelText
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    func setup() {
        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [
            UIViewAutoresizing.flexibleWidth,
            UIViewAutoresizing.flexibleHeight
        ]
        addSubview(view)
    }
    
    func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "MyView", bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
        
        return view
    }
    
}

4.在MyView.xib

a.選擇File;s Owner
b.點擊Identity並選擇Class 對應到 MyView

5.連結Label到MyView.swift
6.於Main.storyboard建立四個按鈕

7.ViewController.swift


//
//  ViewController.swift
//  JumpViewTest
//
//  Created by Terry Yang on 2017/6/30.
//  Copyright © 2017年 terryyamg. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    var indexOfTop : Int    = 0
    var indexOfLeft : Int   = 0
    var indexOfRight : Int  = 0
    var indexOfBottom : Int = 0
    
    var topView : MyView?
    var leftView : MyView?
    var rightView : MyView?
    var bottomView : MyView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    //  Top 按鈕
    @IBAction func btnShowTop(_ sender: UIButton) {
        indexOfTop += 1
        
        if topView == nil {
            //建立客製化View視窗
            topView = MyView(frame: CGRect(x: 0, y: -150, width: UIScreen.main.bounds.size.width, height: 150)) //設定初始位置與大小
            topView?.backgroundColor = .red //背景顏色
            topView?.labCustom.text = "Oh! Top View" //顯示文字
            self.view.addSubview(topView!) //加入此視窗
        }
        
        if indexOfTop % 2 == 1 { //點擊展開視窗
            UIView.animate(withDuration: 0.5, animations: {
                self.topView?.frame.origin.y += 150
            }, completion: nil)
        } else { //點擊收起視窗
            UIView.animate(withDuration: 0.5, animations: {
                self.topView?.frame.origin.y -= 150
            }, completion: nil)
        }
    }
    
    // Left 按鈕
    @IBAction func btnShowLeft(_ sender: UIButton) {
        
        indexOfLeft += 1
        
        if leftView == nil {
            leftView = MyView(frame: CGRect(x: -150, y: 0, width: 150, height: UIScreen.main.bounds.size.height))
            leftView?.backgroundColor = .brown
            leftView?.labCustom.text = "It's Left"
            self.view.addSubview(leftView!)
        }
        
        if indexOfLeft % 2 == 1 {
            UIView.animate(withDuration: 0.5, animations: {
                self.leftView?.frame.origin.x += 150
            }, completion: nil)
        } else {
            UIView.animate(withDuration: 0.5, animations: {
                self.leftView?.frame.origin.x -= 150
            }, completion: nil)
        }
        
    }
    
    // Right 按鈕
    @IBAction func btnShowRight(_ sender: UIButton) {
        
        indexOfRight += 1
        
        if rightView == nil {
            rightView = MyView(frame: CGRect(x: view.frame.maxX, y: 0, width: 150, height: UIScreen.main.bounds.size.height))
            rightView?.backgroundColor = .green
            rightView?.labCustom.text = "It's Right"
            self.view.addSubview(rightView!)
        }
        
        if indexOfRight % 2 == 1 {
            UIView.animate(withDuration: 0.5, animations: {
                self.rightView?.frame.origin.x -= 150
            }, completion: nil)
        } else {
            UIView.animate(withDuration: 0.5, animations: {
                self.rightView?.frame.origin.x += 150
            }, completion: nil)
        }
        
    }
    
    // Bottom 按鈕
    @IBAction func btnShowBootom(_ sender: UIButton) {
        indexOfBottom += 1
        
        if bottomView == nil {
            bottomView = MyView(frame: CGRect(x: 0, y: view.frame.maxY, width: UIScreen.main.bounds.size.width, height: 150))
            bottomView?.backgroundColor = .black
            bottomView?.labCustom.text = "Hi! Bottom View"
            self.view.addSubview(bottomView!)
        }
        
        if indexOfBottom % 2 == 1 {
            UIView.animate(withDuration: 0.5, animations: {
                self.bottomView?.frame.origin.y -= 150
            }, completion: nil)
        } else {
            UIView.animate(withDuration: 0.5, animations: {
                self.bottomView?.frame.origin.y += 150
            }, completion: nil)
        }
    }
    
    
}


 檔案下載:
https://github.com/terryyamg/JumpViewTest
 參考連結:
https://stackoverflow.com/documentation/ios/1362/custom-uiviews-from-xib-files#t=201706280220545656796
https://www.youtube.com/watch?v=EBYdsYwoJVI&t=492s
https://www.youtube.com/watch?v=MMgfnrddOxY

2017年5月23日 星期二

Firebase Cloud Messaging 接收訊息 PHP Service

FCM兩種推播通知方式 - Notification與Data

----------------------------------FCM前置步驟可觀看----------------------------------
Step1:
https://litotom.com/2016/06/24/android-firebase-cloud-messaging-1/
注意:
apply plugin: 'com.google.gms.google-services'
要放在build.gradle(Module: app)這個檔案裡面的最下面

Step2:
https://litotom.com/2016/06/26/firebase-android-send-2/

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

1.在MyFirebaseMessagingService.java加入推播通知訊息
package com.terryyamg.fcmtest;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private static final String TAG = "MyFirebaseMessaging";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Log.i(TAG, "onMessageReceived:" + remoteMessage.getFrom());
        Intent intent = new Intent();   //點擊啟動到MainActivity頁面
        intent.setClass(this, MainActivity.class);
        showNotification(this, remoteMessage, intent);
    }

    // 顯示通知
    // remoteMessage.getData()         -  無論app在什麼狀態下皆會執行 MyFirebaseMessagingService(需對應Service設定的字串文字)
    // remoteMessage.getNotification() -  只會在app顯示時,執行app的接收訊息 MyFirebaseMessagingService
    private void showNotification(Context context, RemoteMessage remoteMessage, Intent intent) {
        NotificationManagerCompat manager = NotificationManagerCompat.from(this);
        PendingIntent iPending = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle(remoteMessage.getData().get("title")) //需對應Service設定的字串文字
                        .setWhen(System.currentTimeMillis())
                        .setDefaults(Notification.DEFAULT_SOUND)
                        .setContentText(remoteMessage.getData().get("body")) //需對應Service設定的字串文字
                        .setContentIntent(iPending)
                        .setAutoCancel(true);

        Notification notification = mBuilder.build();
        manager.notify(1, notification);

    }
}

2.Service推播 fcm_service.php
    #API access key from Google API's Console
    define( 'API_ACCESS_KEY', 'AAAA6iG_xzk:APA91bHxlAkx5byr66cIFk-CXAr2k3hvs4tGQV9nAzj2nw75kPC8_9eIyyZVaeUPYFYzAk10qV-D0Pwl5BsIGslvv32PI2ECap1KcmW7Iu6lyzh1A3eEE2TJ5jVsnPydWFgKjvh3J5hh' );
    $registrationIds = "dTDs6rF7h04:APA91bECmREiN_qgUJ2IBwagJRzWmVxDMWVpmjEnkgJi1FWMLAtFHGTSYVXA-0VTHMf-42mZH8qLBEdKHFs9Z6oCL1JUf0uJ232z26XJdpEuwMvVPFxunwAZPN9E4-6qKImQEb7b9iHw";
    #prep the bundle
    $msg = array
    (
     'body'  => 'Body  Of Notification',
     'title' => 'Title Of Notification'
     );
    //'notification' - 只會在app顯示時,執行app的接收訊息Service
    //'data'         - 無論app在什麼狀態皆會執行app的接收訊息Service
    $fields = array
    (
     'to'  => $registrationIds,
     'data'     => $msg
     );
    
    
    $headers = array
    (
     'Authorization: key=' . API_ACCESS_KEY,
     'Content-Type: application/json'
     );
    #Send Reponse To FireBase Server
    $ch = curl_init();
    curl_setopt( $ch,CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send' );
    curl_setopt( $ch,CURLOPT_POST, true );
    curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
    curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
    curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
    $result = curl_exec($ch );
    curl_close( $ch );
    #Echo Result Of FireBase Server
    echo $result;




檔案下載:
參考資料:




2016年12月19日 星期一

Android library Realm 資料庫

簡單的Realm資料庫CRUD應用

1.build.gradle(Project:xxxx)加入classpath "io.realm:realm-gradle-plugin:2.2.1"
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
        classpath "io.realm:realm-gradle-plugin:2.2.1"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
2.build.gradle(Module:app)加入apply plugin: 'realm-android'
apply plugin: 'com.android.application'
apply plugin: 'realm-android'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"
    defaultConfig {
        applicationId "com.terryyamg.realmtest"
        minSdkVersion 19
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support:design:24.2.1'
    testCompile 'junit:junit:4.12'
}
3.AndroidManifest.xml的application內加入android:name=".MyApplication"


    
        
            
                

                
            
        
    


4.MyApplication.java
package com.terryyamg.realmtest;
import android.app.Application;
import io.realm.Realm;
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // Initialize Realm. Should only be done once when the application starts.
        Realm.init(this);

    }
}
5.MainActivity.java
package com.terryyamg.realmtest;

import android.app.Dialog;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import io.realm.Realm;
import io.realm.RealmResults;

public class MainActivity extends AppCompatActivity {

    private Realm realm;
    TextView tvMessage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        realm = Realm.getDefaultInstance();

        tvMessage = (TextView) findViewById(R.id.tvMessage);

        //Create
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                create();
            }
        });
        //Update
        FloatingActionButton fabUpdate = (FloatingActionButton) findViewById(R.id.fabUpdate);
        fabUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                update();
            }
        });
        //Delete
        FloatingActionButton fabDelete = (FloatingActionButton) findViewById(R.id.fabDelete);
        fabDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                delete();
            }
        });

        read();
    }

    private void create() {
        final Dialog dialog = new Dialog(MainActivity.this);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.table_dialog);
        final EditText etName = (EditText) dialog.findViewById(R.id.etName);
        final EditText etAge = (EditText) dialog.findViewById(R.id.etAge);
        Button btnSave = (Button) dialog.findViewById(R.id.btnSave);
        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        int personCount;
                        //PrimaryKey增加
                        try {
                            personCount = realm.where(Person.class).max("id").intValue() + 1;
                        } catch (Exception e) {
                            personCount = 1; //從1開始
                        }
                        Log.i("personCount", personCount + "");
                        // 人 Table
                        Person personC = realm.createObject(Person.class, personCount);
                        personC.setName(etName.getText().toString());   // 輸入姓名
                        personC.setAge(Integer.parseInt(etAge.getText().toString()));   //輸入年齡

                        /* 公司 Table 測試 */
//                        Company company1 = realm.createObject(Company.class);
//                        company1.setId(4);
//                        company1.setCompanyName("公司2");
//                        personC.getCompany().add(company1);
//
//                        Company company2 = realm.createObject(Company.class);
//                        company2.setId(5);
//                        company2.setCompanyName("公司3");
//                        personC.getCompany().add(company2);
//
//                        Company company3 = realm.createObject(Company.class);
//                        company3.setId(6);
//                        company3.setCompanyName("公司4");
//                        personC.getCompany().add(company3);

                        read();
                        dialog.dismiss();
                    }
                });
            }
        });

        dialog.show();
    }

    private void read() {
        /* 公司 Table 測試 */
//        RealmResults<Person> person = realm.where(Person.class).equalTo("company.companyName", "公司2").equalTo("company.companyName", "公司1").findAll();
        RealmResults<Person> person = realm.where(Person.class).findAll();
        String message = "";
        Log.i("person.size()",person.size()+"");
        for (int i = 0; i < person.size(); i++) {
            message += person.get(i).getName() + " - " + person.get(i).getAge() + "\n";
            /* 公司 Table 測試 */
//            Log.i("getCompany.size()",person.get(i).getCompany().size()+"");
//            for(int j = 0; j <person.get(i).getCompany().size();j++) {
//                message += person.get(i).getName() + " - " + person.get(i).getAge() + ":" + person.get(i).getCompany().get(j).getCompanyName() + "\n";
//            }
        }
        tvMessage.setText(message);
    }

    private void update() {
        final Dialog dialog = new Dialog(MainActivity.this);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.table_dialog);
        final EditText etName = (EditText) dialog.findViewById(R.id.etName);
        final EditText etAge = (EditText) dialog.findViewById(R.id.etAge);
        Button btnSave = (Button) dialog.findViewById(R.id.btnSave);
        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        //修改第一筆
                        Person person = realm.where(Person.class).equalTo("id", 1).findFirst();
                        person.setName(etName.getText().toString());
                        person.setAge(Integer.parseInt(etAge.getText().toString()));

                        read();
                        dialog.dismiss();
                    }
                });
            }
        });

        dialog.show();
    }

    private void delete() {
        final RealmResults<Person> results = realm.where(Person.class).findAll();
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                //刪除某一筆
//                Person p = results.get(1);
//                p.deleteFromRealm();
                //刪除第一筆
//                results.deleteFirstFromRealm();
                //刪除最後一筆
//                results.deleteLastFromRealm();
                //刪除全部
                results.deleteAllFromRealm();
            }
        });
        read();
    }

}

6.Person.java
package com.terryyamg.realmtest;


import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;

public class Person extends RealmObject{

    @PrimaryKey
    private int id;

    private String name;
    private int age;
    private RealmList<Company> company;

    public void setId(int id){
        this.id = id;
    }

    public int getId(){
        return id;
    }

    public void setName(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }

    public void setAge(int age){
        this.age = age;
    }

    public int getAge(){
        return age;
    }

    public void setCompany(RealmList<Company> company){
        this.company = company;
    }

    public RealmList<Company> getCompany(){
        return company;
    }

}

7.Company.java
package com.terryyamg.realmtest;


import io.realm.RealmObject;

public class Company extends RealmObject{

    private int id;

    private String companyName;

    public void setId(int id){
        this.id = id;
    }

    public int getId(){
        return id;
    }

    public void setCompanyName(String companyName){
        this.companyName = companyName;
    }

    public String getCompanyName(){
        return companyName;
    }

}

8./res/layout/activity_main.xml


    

        

    

    

    

    

    



9./res/layout/content_main.xml


    


10./res/layout/table_dialog.xml


    

    

    



參考資料:
https://realm.io/docs/java/latest/#getting-started
檔案下載:
https://github.com/terryyamg/RealmTest

2016年9月4日 星期日

Android Activity Animation 頁面 動畫

頁面Activity與Dialog移動動畫效果

1.在res下建立anim資料夾

2.在anim建立left_1.xml, left_2.xml, right_1.xml, right_2.xml, up_1.xml, down_1.xml檔案
分別為向左移動,向右移動,向上移動與向下移動

3.left_1.xml

    

left_2.xml



right_1.xml

    

right_2.xml

    

up_1.xml

    

down_1.xml

    

4.Dialog需要在value/styles.xml加入
    
5.3個layout頁面
res/layout/activity_main.xml


    

    


res/layout/activity_next.xml


    

res/layout/dialog_layout.xml


    

6.MainActivity.java
package com.terryyamg.activityanimationtest;

import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnNext = (Button) findViewById(R.id.btnNext);
        Button btnDialog = (Button) findViewById(R.id.btnDialog);
        btnNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, NextActivity.class);
                startActivity(intent);
                overridePendingTransition(R.anim.right_1, R.anim.right_2);
            }
        });

        btnDialog.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final Dialog dialog = new Dialog(MainActivity.this);
                dialog.setContentView(R.layout.dialog_layout);
                dialog.getWindow().getAttributes().windowAnimations = R.style.DialogAnimation;
                Button btnClose = (Button) dialog.findViewById(R.id.btnClose);
                btnClose.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        dialog.dismiss();
                    }
                });
                dialog.show();
            }
        });


    }
}

NextActivity.java
package com.terryyamg.activityanimationtest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class NextActivity extends AppCompatActivity {

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

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        overridePendingTransition(R.anim.left_1, R.anim.left_2);
    }

}



檔案下載:
 https://github.com/terryyamg/ActivityAnimationTest
參考來源:
http://stackoverflow.com/questions/26431017/how-to-achieve-right-to-left-animation-to-start-the-activity
http://android-er.blogspot.tw/2012/07/dialog-animation-using-windowanimations.html

2016年7月26日 星期二

Android Transitions Scene 動態UI

Android的api: android.transition 提供基本的UI動畫 Min Sdk Version: API 21: Android 5.0 (Lollipop) -才有Slide,Explode...
1.activity_main.xml


    

    

    

    

    


2.scene2.xml


    

    

    

    

    


3.MainActivity.java
package com.terryyamg.transitionstest;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.transition.Explode;
import android.transition.Scene;
import android.transition.Slide;
import android.transition.TransitionManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {
    private ViewGroup transitionsContainer;
    private Scene scene1, scene2;
    private Button btnShowBottomText, btnSlide;
    private TextView tv1;

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

        transitionsContainer = (ViewGroup) findViewById(R.id.transitions_container);
        scene1 = Scene.getSceneForLayout(transitionsContainer, R.layout.activity_main, this); //初始畫面
        scene2 = Scene.getSceneForLayout(transitionsContainer, R.layout.scene2, this); //第二畫面
        setting();

    }

    private void setting() {
        btnShowBottomText = (Button) transitionsContainer.findViewById(R.id.btnShowBottomText);
        tv1 = (TextView) transitionsContainer.findViewById(R.id.tv1);
        btnSlide = (Button) transitionsContainer.findViewById(R.id.btnSlide);
    }

    public void showBottomText(View view) {
        Log.i("show", "showBottomText");
        TransitionManager.beginDelayedTransition(transitionsContainer); //預設 new Fade()
        toggleVisibility(tv1);
    }

    public void slide(View view) {
        Log.i("show", "slide");
        TransitionManager.beginDelayedTransition(transitionsContainer, new Slide()); //滑動 ex:Slide(Gravity.RIGHT)..
        toggleVisibility(btnShowBottomText);
    }

    public void explode(View view) {
        Log.i("show", "explode");
        TransitionManager.beginDelayedTransition(transitionsContainer, new Explode()); //爆炸 隨意方向
        toggleVisibility(btnShowBottomText, btnSlide);
    }

    public void goToScene1(View view) {
        TransitionManager.go(scene1); //回到初始頁面
        setting();
    }

    public void goToScene2(View view) {
        TransitionManager.go(scene2); //前往第二頁面
        setting();
    }

    private static void toggleVisibility(View... views) {
        for (View view : views) {
            boolean isVisible = view.getVisibility() == View.VISIBLE;
            view.setVisibility(isVisible ? View.GONE : View.VISIBLE); //隱藏或顯示
        }
    }

}



檔案下載:
https://github.com/terryyamg/TransitionsTest
參考連結:
http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html
https://possiblemobile.com/2013/11/new-transitions-framework/
http://www.techotopia.com/index.php/Implementing_Android_Scene_Transitions_%E2%80%93_A_Tutorial

2016年6月12日 星期日

Android WearableListView 手錶 列表

建立Android手錶列表清單(圖+文字)

1.建立一個空白手錶專案
2.修改/res/layout/rect_activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:background="#FFFFFF"
    android:orientation="vertical"
    tools:context="com.terryyamg.wearablelistviewtest.MainActivity"
    tools:deviceIds="wear_square">

    <android.support.wearable.view.WearableListView
        android:id="@+id/wearable_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></android.support.wearable.view.WearableListView>

</LinearLayout>
3.修改/res/layout/round_activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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:background="#FFFFFF"
    tools:context="com.terryyamg.wearablelistviewtest.MainActivity"
    tools:deviceIds="wear_round">

    <android.support.wearable.view.WearableListView
        android:id="@+id/wearable_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></android.support.wearable.view.WearableListView>

</RelativeLayout>
4.建立/res/layout/list_item.xml
<com.terryyamg.wearablelistviewtest.WearableListItemLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:gravity="center_vertical">

    <android.support.wearable.view.CircledImageView
        android:id="@+id/iv"
        android:layout_width="50dip"
        android:layout_height="50dip"
        android:layout_gravity="center_vertical"
        android:layout_margin="20dp"></android.support.wearable.view.CircledImageView>

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginRight="16dp"
        android:fontFamily="sans-serif-condensed-light"
        android:gravity="center_vertical|left"
        android:lineSpacingExtra="-4sp"
        android:textColor="#000000"
        android:textSize="16sp"></TextView>
</com.terryyamg.wearablelistviewtest.WearableListItemLayout>
5.新增WearableListItemLayout.java
package com.terryyamg.wearablelistviewtest;

import android.content.Context;
import android.support.wearable.view.CircledImageView;
import android.support.wearable.view.WearableListView;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;

public class WearableListItemLayout extends LinearLayout
        implements WearableListView.OnCenterProximityListener {

    private CircledImageView mImageView;
    private TextView mTextView;

    public WearableListItemLayout(Context context) {
        this(context, null);
    }

    public WearableListItemLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WearableListItemLayout(Context context, AttributeSet attrs,
                                  int defStyle) {
        super(context, attrs, defStyle);

    }

    // Get references to the icon and text in the item layout definition
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // These are defined in the layout file for list items
        // (see next section)
        mImageView = (CircledImageView) findViewById(R.id.iv);
        mTextView = (TextView) findViewById(R.id.tv);
    }

    @Override
    public void onCenterPosition(boolean animate) {
        //在中間
        mImageView.setScaleX(1f);
        mImageView.setScaleY(1f);
        mTextView.setAlpha(1f);
    }

    @Override
    public void onNonCenterPosition(boolean animate) {
        //其他部分 圖縮小 文字透明一半
        mImageView.setScaleX(0.8f);
        mImageView.setScaleY(0.8f);
        mTextView.setAlpha(0.5f);
    }
}
6.新增MyAdapter.java
package com.terryyamg.wearablelistviewtest;

import android.content.Context;
import android.support.wearable.view.CircledImageView;
import android.support.wearable.view.WearableListView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class MyAdapter extends WearableListView.Adapter {
    private int[] mImage;
    private String[] mDataset;
    private final Context mContext;
    private final LayoutInflater mInflater;

    // Provide a suitable constructor (depends on the kind of dataset)
    public MyAdapter(Context context,int[] imageData, String[] dataset) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mImage = imageData;
        mDataset = dataset;
    }

    // Provide a reference to the type of views you're using
    public static class ItemViewHolder extends WearableListView.ViewHolder {
        private CircledImageView imageView;
        private TextView textView;

        public ItemViewHolder(View itemView) {
            super(itemView);
            // find the text view within the custom item's layout
            imageView = (CircledImageView) itemView.findViewById(R.id.iv);
            textView = (TextView) itemView.findViewById(R.id.tv);
        }
    }

    // Create new views for list items
    // (invoked by the WearableListView's layout manager)
    @Override
    public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                          int viewType) {
        // 對應list_item layout
        return new ItemViewHolder(mInflater.inflate(R.layout.list_item, null));
    }

    // Replace the contents of a list item
    // Instead of creating new views, the list tries to recycle existing ones
    // (invoked by the WearableListView's layout manager)
    @Override
    public void onBindViewHolder(WearableListView.ViewHolder holder,
                                 int position) {
        // retrieve the text view
        ItemViewHolder itemHolder = (ItemViewHolder) holder;
        CircledImageView iv = itemHolder.imageView;
        TextView view = itemHolder.textView;
        // 放入圖片、文字
        iv.setImageResource(mImage[position]);
        view.setText(mDataset[position]);
        // 設定tag id
        holder.itemView.setTag(position);
    }

    // Return the size of your dataset
    // (invoked by the WearableListView's layout manager)
    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

7.放入三張圖片(google.png,facebook.png,twitter.png)在/res/drawable/

8.MainActivity.java
package com.terryyamg.wearablelistviewtest;

import android.app.Activity;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.view.WearableListView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private int[] ivItems;
    private String[] tvItems;

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

        ivItems = new int[]{R.drawable.google, R.drawable.facebook, R.drawable.twitter}; //圖
        tvItems = new String[]{"Google", "Facebook", "Twitter"}; //文字

        final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
        stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
            @Override
            public void onLayoutInflated(WatchViewStub stub) {
                // Get the list component from the layout of the activity
                WearableListView listView =
                        (WearableListView) stub.findViewById(R.id.wearable_list);

                // Assign an adapter to the list
                listView.setAdapter(new MyAdapter(MainActivity.this, ivItems, tvItems));
                listView.setClickListener(new WearableListView.ClickListener() {
                    @Override
                    public void onClick(WearableListView.ViewHolder viewHolder) {
                        Integer tag = (Integer) viewHolder.itemView.getTag();
                        Toast.makeText(MainActivity.this,tvItems[tag],Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onTopEmptyRegionClick() {

                    }
                });
            }
        });
    }
}

檔案下載:
https://github.com/terryyamg/WearableListViewTest
參考來源:
https://developer.android.com/training/wearables/ui/lists.html
http://www.learnandroidwear.com/sample-1/
https://gist.github.com/PomepuyN/c30760eaee1e58fdd8fa