2018年2月26日 星期一

[Android] TextView 的 freezesText

今天在處理當螢幕旋轉問題時,意外看到freezesText這個設定。

先來看一下Android的文件上怎麼說明的

android:freezesText
If set, the text view will include its current complete text inside of its frozen icicle in addition to meta-data such as the current cursor position. By default this is disabled; it can be useful when the contents of a text view is not stored in a persistent place such as a content provider. For EditText it is always enabled, regardless of the value of the attribute.
May be a boolean value, such as "true" or "false".

預設這個設定是關閉的,當你開啟時,它會完整保留TextView上的內容,遊標位置除外。

在EditText中,這個設定總是開啟的。

預設情況下,當我們用在執行過程中,透過程式去設定TextView內容後,當螢幕進行旋轉後,

TextView的內容會消失.


以下是範例程式碼,當按下Add Button後,會把TextView的內容+1
   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView textView=(TextView)findViewById(R.id.txt);

        Button button=(Button)findViewById(R.id.add);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int num=0;
                if (!textView.getText().toString().equals(""))
                {
                    num=Integer.parseInt(textView.getText().toString());
                }



                num++;

                textView.setText(String.valueOf(num));

            }
        });
    }

例如我按了六下Button,目前TextView的內容是6


當我進行螢幕旋轉後,TextView的內容不見囉



如果我將freezesText設為True


<?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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.boywhychen.context_menu.MainActivity">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/txt"
        android:freezesText="true"
        />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/add"
        android:text="Add"
        android:layout_below="@+id/txt"
        />
</RelativeLayout>

執行結果如下


它會保留TextView的內容。

參考資料:

https://developer.android.com/reference/android/widget/TextView.html

2018年2月25日 星期日

[Android] String Format 中的 $

一般來說如果我們要透過String.Format 作字串格式化顯示 , 例如我們要顯示

1 + 10 = 11




我們會這樣用
String formatStr="%d + %d = %d";
TextView textView=(TextView)findViewById(R.id.txt);
String text=String.format(formatStr,1,10,11); textView.setText(text);

但也可以透過$指定索引順序,效果相同

String formatStr="%1$d + %3$d = %2$d";
TextView textView=(TextView)findViewById(R.id.txt);
String text=String.format(formatStr,1,11,10);
textView.setText(text);

2018年2月24日 星期六

[Android] FusedLocationProviderClient

FusedLocationProviderClient

The 11.0.0 release of the Google Play services SDK includes a new way to accessLocationServices. The new APIs do not require your app to manually manage a connection to Google Play services through a GoogleApiClient. This reduces boilerplate and common pitfalls in your app.


  • The API calls automatically wait for the service connection to be established, which removes the need to wait for onConnected before making requests.
  • It uses the Task API which makes it easier to compose asynchronous operations.
  • The code is self-contained and could easily be moved into a shared utility class or similar.
  • You don't need to understand the underlying connection process to start coding.

它是新的存取位置的API,整合在Google Play Service中,不再因為API不同而在過去使用LocationManager上有所差異。

2018年2月20日 星期二

[iOS]使用Google Map

使用GOOGLE的API,萬法不離宗,就是要申請API KEY,

所以到Google Cloud Platform 新增專案
https://console.developers.google.com

















建立完成後畫面如下,接下來點選啟用API和服務



















點選Google Map for iOS



















啟用


建立憑證

點選"我需要那些憑證?"



點選"為金鑰新增限制"保護你的KEY不被預期之外的程序呼叫

如果你有要限定某一款iOS APP 才能呼叫,記得在com.example.MyAPP填入

 金鑰這樣就申請好囉


接下來記住你的金鑰,進入軟體部份了,如果您沒安裝過CocoaPods 需要先安裝,否則

請跳過安裝這一步,記得要是Xcode 8 or 9 ,請在終端機打上以下內容

sudo gem install cocoapods

等待安裝完成。

接下來我們要開啟一個Xcode專案


當專案建立完畢後,關閉專案及Xcode,回到終端機,進入專案根目錄下

cd MapDemo/

接下來要安裝GOOGLE MAP SDK,首先初始化一下cocoa pods 需要的設定檔
pod init

接下來編輯設定檔
vi Podfile

設定檔一開始長這樣
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'MapDemo' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MapDemo

  target 'MapDemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'MapDemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end
~      

加入GOOGLE MAP SDK,主要有三行
source 'https://github.com/CocoaPods/Specs.git'

pod 'GoogleMaps'
pod 'GooglePlaces' 


# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
source 'https://github.com/CocoaPods/Specs.git'
target 'MapDemo' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!
  pod 'GoogleMaps'
  pod 'GooglePlaces' 
  # Pods for MapDemo

  target 'MapDemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'MapDemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end




# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'MapDemo' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MapDemo

  target 'MapDemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'MapDemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end
~      

接下來存檔,然候開始安裝SDK囉,視網路情況,可能需要一點時間喔。
pod install
安裝過程如下

Analyzing dependencies
Downloading dependencies
Installing GoogleMaps (2.5.0)
Installing GooglePlaces (2.5.0)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `MapDemo.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed.

[!] Automatically assigning platform ios with version 10.2 on target MapDemo because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.



接下來用指令啟動xocde
open MapDemo.xcworkspace

安裝成功的話你會發現專案多了一個Pods


在AppDelegate.swift 加入import GoogleMaps



接下來在application(_:didFinishLaunchingWithOptions:) 方法中填入你的API KEY(金鑰)

GMSServices.provideAPIKey("YOUR_API_KEY")









接下來把Main.storyboard的View的Class改成GMSMapView


接著在ViewController.swift中加入import GoogleMaps 以及以下程式碼

  override func loadView() {
        // Create a GMSCameraPosition that tells the map to display the
        // coordinate -33.86,151.20 at zoom level 6.
        let camera = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 6.0)
        let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
        mapView.isMyLocationEnabled = true
        view = mapView
        
        // Creates a marker in the center of the map.
        let marker = GMSMarker()
        marker.position = CLLocationCoordinate2D(latitude: -33.86, longitude: 151.20)
        marker.title = "Sydney"
        marker.snippet = "Australia"
        marker.map = mapView

    }


完整ViewController.swift如下


接著在Info.plist中加入Google Maps SDK for iOS 使用的 URL 配置


或是用UI介面編輯


接下來就可以開啟模擬器執行看看地圖會不會出來囉,地點會在雪梨


接下來我們試著顯示自己位置看看,此時我們會需要再加入一個NSLocationAlwaysUsageDescription 到Info.plis 提示用戶需要位置權限的字串



接下來我們改一下程式碼如下
import UIKit import GoogleMaps class YourViewController: UIViewController {   // You don't need to modify the default init(nibName:bundle:) method.   override func loadView() {     // Create a GMSCameraPosition that tells the map to display the     // coordinate -33.86,151.20 at zoom level 6.     let camera = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 6.0)     let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)     mapView.isMyLocationEnabled = true     view = mapView     // Creates a marker in the center of the map.     let marker = GMSMarker()     marker.position = CLLocationCoordinate2D(latitude: -33.86, longitude: 151.20)     marker.title = "Sydney"     marker.snippet = "Australia"     marker.map = mapView   } }

//
//  ViewController.swift
//  MapDemo
//
//  Created by boywhy chen on 2018/2/20.
//  Copyright © 2018年 qq. All rights reserved.
//

import UIKit
import GoogleMaps

class ViewController: UIViewController,CLLocationManagerDelegate {
    var locationManager = CLLocationManager()
    var currentLocation: CLLocation?
    var mapView: GMSMapView!
    var zoomLevel: Float = 15.0
    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.
    }
    
    override func loadView() {
        locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestAlwaysAuthorization()
        locationManager.distanceFilter = 50
        locationManager.startUpdatingLocation()
        locationManager.delegate = self
        
     
        
        //init MapView
        let camera = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 6.0)
        
        mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
        
        mapView.isMyLocationEnabled = true
        
        view = mapView
    }
    
    // Handle incoming location events.
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location: CLLocation = locations.last!
        print("Location: \(location)")
        
        let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
                                              longitude: location.coordinate.longitude,
                                              zoom: zoomLevel)
        
        if mapView.isHidden {
            mapView.isHidden = false
            mapView.camera = camera
        } else {
            mapView.animate(to: camera)
        }
        
      
    }
    
    // Handle authorization for the location manager.
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .restricted:
            print("Location access was restricted.")
        case .denied:
            print("User denied access to location.")
            // Display the map using the default location.
            mapView.isHidden = false
        case .notDetermined:
            print("Location status not determined.")
        case .authorizedAlways: fallthrough
        case .authorizedWhenInUse:
            print("Location status is OK.")
        }
    }
    
    // Handle location manager errors.
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        locationManager.stopUpdatingLocation()
        print("Error: \(error)")
    }
}

主要是增加定位功能,接下來執行會要求權限,按下Allow


如果是用模擬器的話,可以選置以下幾種方式模擬位置

APPLE ,定位在APPLE總部




Custom Location 則是自己輸入經緯度



City Bike Ride ,City Run

其實是類似功能,只是移動速度不太一樣以及移動路徑不太相同


Freeway Driver 則是模擬在高速公路開車




參考文件

https://developers.google.com/maps/documentation/ios-sdk/map?hl=zh-tw

https://developers.google.com/maps/documentation/ios-sdk/current-places-tutorial?hl=zh-tw

https://stackoverflow.com/questions/24062509/location-services-not-working-in-ios-8

2018年2月17日 星期六

[Android] Context Menu

最近看到一個蠻特別的東西--Context Menu--,沒想到可以這樣用,
幫Button加入"長按"選單




首先這是MainActivity的layout,XML的部份,只有一個Button

<?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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.boywhychen.context_menu.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/menu_btn"
        android:text="Context Menu" />
</RelativeLayout>

接下來我們需要在menu資料來下建立一個my_menu.xml


內容就是列出Menu中要顯示的Item內容以及順序

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/color_blue"
    android:title="Blue"
    android:orderInCategory="1"
    app:showAsAction="ifRoom"/>
<item android:id="@+id/color_red"
    android:title="Red"
    android:orderInCategory="2"
    app:showAsAction="ifRoom"/>
<item android:id="@+id/color_green"
    android:title="Green"
    android:orderInCategory="3"
    app:showAsAction="ifRoom"/>
</menu>

接下來是主程式

package com.example.boywhychen.context_menu;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {



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

        //初始化Button
        Button menu_btn=(Button)findViewById(R.id.menu_btn);
        //幫Button註冊ContextMenu
        registerForContextMenu(menu_btn);
    }


    @Override
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        //要使用的Context Menu XML檔案
        getMenuInflater().inflate(R.menu.my_menu,menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        //按下Context Menu Item要做的事
        switch ((item.getItemId()))
        {
            case R.id.color_blue:
                Toast.makeText(MainActivity.this,"Blue",Toast.LENGTH_SHORT).show();
                break;
            case R.id.color_green:
                Toast.makeText(MainActivity.this,"Green",Toast.LENGTH_SHORT).show();
                break;
            case R.id.color_red:
                Toast.makeText(MainActivity.this,"Red",Toast.LENGTH_SHORT).show();
                break;
        }

        return super.onContextItemSelected(item);
    }
}

執行效果(記住是長按才會跳出選單~我一開始都用click都沒反應~搞了半天)

點下Red後,Toast會顯示你按下的是Red




參考來源:

The Complete Android Material Design Course: Become a Pro
https://www.udemy.com/android-material-design-course-tutorial/learn/v4/overview
上課心得筆記一下

2018年2月3日 星期六

「HTML5][Java] Server-Sent Events

Server-Sent Events

W3Schools上的說明:

server-sent event is when a web page automatically gets updates from a server.
This was also possible before, but the web page would have to ask if any updates were available. With server-sent events, the updates come automatically.
Examples: Facebook/Twitter updates, stock price updates, news feeds, sport results, etc.


簡單來說SSE的特點為"One Way Messaging" ,也就是Client端被動接收Server端訊息。

過去HTTP 都是一個單一回合結束,如果要取得新的資料,必須重新發送一次

HTTP Request,而SSE則是可以持續接收Server資訊,無須主動發送Request ,

但Client端無法透過SSE與Server端進行溝通,這是與webSocket不同的地方。

以下有一個範例,利用Java Servlet 透過SSE的方式與Client端JavaScript溝通.

當下按Start Button後,JavaScript會開始接收由Server端傳送過來的SSE資料,並更新

在網頁上。

Client端程式碼
<!DOCTYPE HTML>
<html>
    <head>
        <title>Server-Sent Events Servlet example</title>
        <script>
            function start() {
                var eventSource = new EventSource("Server");
                eventSource.onmessage = function (event) {
                    document.getElementById('myDiv').innerHTML = event.data;
                }
            }
            ;

        </script>
        <style>
            body {
                font-family: sans-serif;
            }
        </style>
    </head>
    <body>

        Time: <span id="myDiv"></span>

        <br><br>
        <button onclick="start()">Start</button>


    </body>
</html>


Server端Servlet程式碼
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author boywhychen
 */
public class Server extends HttpServlet {

    /**
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code>
     * methods.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");

        PrintWriter writer = response.getWriter();

        for (int i = 0; i < 20; i++) {

            writer.write("data: " + getTime() + "\n\n");
            writer.flush();

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        writer.close();
    }

    private String getTime() {
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(cal.getTime());
    }

    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /**
     * Handles the HTTP <code>GET</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns a short description of the servlet.
     *
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>

}


參考資料來源:

https://gist.github.com/viralpatel/7007662