2018-12-30

【Oracle】撈出例假日的語法

函式說明
 
TO_DATE(string1 [, format_mask] [, nls_language]):將字串轉成日期型態
第 1 個參數是 轉換的字串 的值
第 2 個參數是您的欄位, 字串的 年, 月, 日 的排列
TO_DATE(enter_date,'YYYYMMDD')



TO_CHAR(value [, format_mask] [, nls_language] ):將日期型態轉成字串
第 1 個參數是 要轉換的日期型態 的值
第 2 個參數是 要轉換的 格式, 其中 'D' 是指一週的第幾天
星期日是 1, 星期一是 2, 星期六是 7
 



 
SELECT  DISTINCT enter_date, TO_CHAR(TO_DATE(enter_date,'YYYYMMDD'),'D') dayofweek FROM table
    WHERE enter_date BETWEEN '20180101' AND '20181231'
        AND TO_CHAR(TO_DATE(enter_date,'YYYYMMDD'),'D') IN (1,7)
    ORDER BY enter_date
 

2018-12-25

【Android Studio】將 Java 程式碼轉換成 Kotlin 碼

參考資料 ----
Convert existing Java code to Kotlin code


打開要轉換成 KotlinJava 程式檔
功能表 Code
Convert Java File to Kotlin File

Android Studio 就會自動將 Java 程式碼轉換成 Kotlin 程式碼了



可以藉由這種方式,觀摹並對照 Kotlin 風格的程式寫法

2018-12-23

【Kotlin】設定 AdMob 廣告



Kotlin 是目前 Google 力推的開發 Android APP 的語言,語法比 Java 更簡潔


* 建立一個新專案


* 輸入專案名稱 Test,選擇專案放置目錄為 D:\test\kotlin\Test,注意 include Kotlin support 要打勾


* 指定最低相容裝置,在本例為 JellyBean4.1(API16)


* 選擇 Activity 樣本


* 設定 Activity 的名字,在本例為 MainActivity,並且不打算相容舊版,所以 Backwards Compatibility(AppCompat) 的打勾取消


* 開啟 strings.xml,加入 AdMob測試帳號
 
<resources>
    <string name="app_name">Test</string>

    <!--  adMob app id 測試  -->
    <string name="admob_app_id">ca-app-pub-3940256099942544~3347511713</string>
    <!-- 橫幅 測試 -->
    <string name="banner_ad_unit_id">ca-app-pub-3940256099942544/6300978111</string>
    <!-- 插頁 測試 -->
    <string name="interstitial_ad_unit_id">ca-app-pub-3940256099942544/8691691433</string>
    <!-- 影片 測試 -->
    <string name="video_ad_id">ca-app-pub-3940256099942544/5224354917</string>
</resources>
 



* 切換成 Project 模式,打開 app 層級的 build.gradle,加入 play-services-ads

 
dependencies {
    ...
    ...

    // AdMob
    implementation 'com.google.android.gms:play-services-ads:17.1.2'
}
 


* 開啟 AndroidManifest.xml
 
<application
    ...
    ... />

    <!-- AdMob -->
    <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="@string/admob_app_id"/>
 




* 開啟 activity_main.xml,置換成下述內容,注意第 6 行

xmlns:ads="http://schemas.android.com/apk/res-auto"
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:ads="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".QbankMainActivity">

    <com.google.android.gms.ads.AdView
            android:id="@+id/adView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true"
            ads:adSize="SMART_BANNER"
            ads:adUnitId="@string/banner_ad_unit_id" />


</RelativeLayout>
 


開啟 MainActivity.kt
 
package com.example.test

import android.app.Activity
import android.os.Bundle
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.MobileAds
import kotlinx.android.synthetic.main.activity_main.*    // 注意這一行

class MainActivity : Activity() 
{

    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_qbank_main)

        // AdMob 初始化
        MobileAds.initialize(this, getString(R.string.admob_app_id));

        // 橫幅廣告
        // 透過第 8 列的 import, 直接參照到 adView, 而不用再 findViewById
        adView.loadAd(AdRequest.Builder().build())
    }
 



2018-12-09

【AdMob】設定 Applovin 中介服務

參考資料 ----
Integrating AppLovin with Mediation



Android SDKAPI 等級需在 API14(ICS4.0.3, 含) 以上

APP 層級的 build.gradle
 
...
...

dependencies {
    ...
    ...

    implementation 'com.applovin:applovin-sdk:9.1.0'
    implementation 'com.google.ads.mediation:applovin:8.1.4.0'
}
 


AndroidManifest.xml
 
...
...
<application
    ...
    ...

    <!-- AppLovin -->
    <meta-data android:name="applovin.sdk.key"
                  android:value="Hos78dCUoIz06RoEMyKlTxOzMFUO3A6??????????XO2zAHK6b4aWSTui1HmBO68LZDxE??????????-5gTc85"  />

        ...
        ...
 


MainActivity.java
 
import com.applovin.sdk.AppLovinSdk;

...
...

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

        // AppLovin 初始化
        AppLovinSdk.initializeSdk(this);
        ...
        ...
 
啟動您的 APP,等候約 20 ~ 30 分鐘後,登入 AppLovin,在 AppLovindashboard ,輸入 APP 完整的 package 名稱, 應該就可以搜尋到。



切換到 Account,複製 SDK Key




此時,可以移除 MainActivity.javaonCreate

AppLovinSdk.initializeSdk(this);



* 登入 AdMob,設定中介服務



點選 AppLovin,輸入 Report Key




回到 AppLovindashboard
→ 點擊 Monetize
Manage Apps
→ 切換為 Test Mode, 等候約 30 分鐘後生效;若沒切換為 Test Mode,則約 2 小時後生效





懶得等了,直接打包更新 Google Play,之後三不五時瞄一下橫幅廣告,就會發現有不同的橫幅出現


AdMob 的橫幅,右上角有個 「X」的圖示




AppLovin 的橫幅,廣告資訊的圖示只有 1 個


點擊 i 圖示 後,就可以看到這是 AppLovin 的廣告


不過,廣告收入似乎也分散了,原以為所有中介廣告費統一由 AdMob 發放,看來好像是各發各的,廣告計算又是每日歸零。

例如:
本來只有 AdMob 時,一天可以有一美元,廣告流量分散到 2 家後,大部份廣告來源仍是 AdMob,只有極小量來自 AppLovin,而 AppLovin 的量又達不到最低的 0.01 美元門檻,所以當日在 AppLovin 的廣告等於是白播放 & 白點擊 的了。

所以用戶少的 APP,啟用中介服務似乎不是很划算,這個有待觀察。

==============
2019.10.31 補充:
經過一年的觀察,並與 Applovin 客服確認,Applovin 著重在 APP 的廣告,所以計算方式是以客戶觀看廣告後有安裝 APP 才有效,光是觀看及點擊廣告是不列入計算的。

2018-12-04

【jQuery】radio button group 的操作

JQuery

 
<script>
    $(function()
    {   // 當 radio 被選取時, 文字變成 藍色
        $('input:radio[name=buy_type]').click(function() 
                                            {
                                                $('.rgpBuytype').css('color', 'black');     // 所有的 radio 文字回復成 黑色
                                               
                                                // 選取的 radio 文字變 藍色
                                                // 先取得被選取的 radio button 的 value
                                                // 將 value 轉換成 div 的 id
                                                $('#'+$(this).val()).css('color', 'blue');
                                            });
    });
</script>

...
...
 




HTML

所有同一組的 radio buttonname 需相同
外面包一個 div,並且宣告其 class,可以對所有同組的 radio button 的屬性做同時且一致的變化,在本例中,就是讓所有 radio button 的文字變成黑色
 
<div class="rgpBuytype" id="ans1" ><input type="radio" name="buy_type" value="ans1" />答案一</div>
<div class="rgpBuytype" id="ans2" ><input type="radio" name="buy_type" value="ans2" />答案二</div>
<div class="rgpBuytype" id="ans3" ><input type="radio" name="buy_type" value="ans3" />答案三</div>
 


取得被選取的 radio button 的值的方法
 
var buy_type = $('input:radio[name=buy_type]:checked').val();
 

2018-11-17

【VisualStudioCode】設定功能表語言為中文

Ctrl + Shift + P 螢幕中央上方會顯示一個小輸入欄



輸入 display 後按 ENTER 鍵

VS Code 會打開一個 locale.json 檔,輸入如下內容
 
{
    // 定義 VS Code 的顯示語言。
    // 如需支援的語言清單,請參閱 https://go.microsoft.com/fwlink/?LinkId=761051。
 
    "locale":"zh-tw" // 在重新啟動 VS Code 前,變更不會生效。
}
 
重新啟動 VS Code 就生效了。

2018-08-11

【Python】CentOS7 安裝 Python3

參考資料 ----
How to install Python 3 on CentOS 7
軟件選集(SCL)軟件庫
How can I make a Red Hat Software Collection persist after a reboot/logout?


想在 CentOS7 使用 Python3 是有點小麻煩的。

RedHat 的套件政策是:主版本週期定為 10 年,主版本釋出後,所包含的套件都會維持主版本不變;例如:CentOS7 內含的 Python 版本是 2.7,在這 10 年間,Python 的版本就會是 2.7.x 的更新。

Python 因為太好用,所以許多套件將 Python 綁在一起,如果想要移除 Python2,會因為套件的相依性而連帶移除了其他原本不想或不能移除的套件!例如:重要的套件管理 yum

但是,10 年是會有滄海滄田的變化的,雖然 RedHat / CentOS 的主要對象是企業,企業重視系統的穩定使用,同時也希望在下一個主版本釋出前,可以對新工具 / 新功能進行 開發 / 測試 / 研究 / 實驗。

RedHat 聽到了企業的聲音,推出了 Software Collections(SCL)SCL 包含了新的套件可供安裝,其中 Python3 的版本,目前更新到 3.6


[root]# vi  /etc/yum.repos.d/CentOS-Base.repo
→ 找到 [extras]
將 enabled 值改成 1
:x 存檔離開


[root]# yum  install  centos-release-scl


偷懶,安裝所有以 rh-python36 字首的套件  :-P

[root]# yum  install  rh-python36*


此時 Python2Python3並存 CentOS7 內的,這時若查詢系統預設執行的 Python 版本

[root]# python  --version

會回應顯示 python 2.7.5



若要以 Python3 執行,則要先下指令:

[root]# scl  enable  rh-python36  bash

再查詢一次

[root]# python  --version

這次就會回應 python 3.6.3

但因為 SCL  原意就是讓我們可以同時使用 Python2Python3,所以當您 登出 或 重新開機,預設要執行的 Python 版本又會回到 2.7


若要每一次登入,預設執行的 Python 版本都是 Python3,方法如下(不過 RedHat 也說這是解套方法,他們還沒想出正解):


建立 rh-python36.sh

[root]# vi /etc/profile.d/rh-python36.sh
 
#!/bin/bash
source  scl_source  enable  rh-python36
 
:x 存檔離開

登出系統,再重新登入

[root]# python  --version
系統就會回應

python 3.6.3



方法二:

Python3 會安裝在下述的路徑

/opt/rh/rh-python36/root/bin/python

在您撰寫的 python 程式的第一行指定要執行的 Python 直譯器

例如,您的程式名為 test.py
 
#!/opt/rh/rh-python36/root/bin/python
import sys
import platform

print(platform.python_version())
 

然後,變更 test.py 的屬性為可執行

[root]# chmod  a+x  test.py

這樣,您的程式會變成可執行檔

執行方式為:切換到 test.py 所在的目錄下,直接執行 test.py

[root]# ./test.py

2018-07-31

【Android】AdMob 的測試帳號

參考資料 ----
Sample ad units for Android
Test Ads for iOS


Android
APP idca-app-pub-3940256099942544~3347511713
Ad format Sample ad unit ID
Banner ca-app-pub-3940256099942544/6300978111
Interstitial ca-app-pub-3940256099942544/1033173712
Interstitial Video ca-app-pub-3940256099942544/8691691433
Rewarded Video ca-app-pub-3940256099942544/5224354917
Native Advanced ca-app-pub-3940256099942544/2247696110
Native Advanced Video ca-app-pub-3940256099942544/1044960115


iOS
Ad format Sample ad unit ID
Banner ca-app-pub-3940256099942544/2934735716
Interstitial ca-app-pub-3940256099942544/4411468910
Interstitial Video ca-app-pub-3940256099942544/5135589807
Rewarded Video ca-app-pub-3940256099942544/1712485313
Native Advanced ca-app-pub-3940256099942544/3986624511
Native Advanced Video ca-app-pub-3940256099942544/2521693316

【Android Studio】All com.android.support libraries must use the exact same version specification 的解決方法


在我的 app 層級build.gradle
 
...
...

dependencies {
    implementation 'com.android.support:support-v4:27.1.1'

    ...
    ...
}
 

出現錯誤訊息
 
All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions 27.1.1, 26.1.0. Examples include com.android.support:recyclerview-v7:27.1.1 and com.android.support:customtabs:26.1.0 less... (Ctrl+F1)

There are some combinations of libraries, or tools and libraries, that are incompatible, or can lead to bugs. One such incompatibility is compiling with a version of the Android support libraries that is not the latest version (or in particular, a version lower than your targetSdkVersion).
 

關鍵在於
com.android.support:customtabs:26.1.0

com.android.support:support-v4:27.1.1
版本不同

所以再加入 customtabs 並指定版本為 27.1.1 即可,如下:
 
...
...

dependencies {
    implementation 'com.android.support:support-v4:27.1.1'
    implementation 'com.android.support:customtabs:27.1.1'

    ...
    ...
}
 

2018-07-12

【Delphi】Delphi7 透過 HTTPS 傳 LINE 訊息

參考資料 ----
download SSL
LINE Messaging API
Indy 10.6 HTTPS POST example with JSON body


這篇筆記有 2 個重點:
1. Delphi7 以 HTTPS 傳遞 post 資料
2. 透過 LINE Messaging API 傳送訊息


開發環境 ----
WinXP 32bit, Delphi7




申請 LINE 開發者帳號(關於 LINE 的相關流程,是老人家的同事處理的,老人家只是依據同事的說法概略的描述流程,更進一步詳細的做法請上 LINE 官網),這是免費的,不過只能傳給 50 個對象,這 50 個對象可以是 個人群組,因為只是做公司內部消息佈達用,所以我們的做法是建立一個群組(省著點用),再將相關的人拉進群組內,發訊息給這個群組就可以了。



在您的官網放置 line.php,目的是要接收取得所建立的 LINE 群組的 ID,並寫入 line.txt
 
<?php
echo "LINE";
$fp = fopen('line.txt' , "a+");
fwrite($fp , print_r( apache_request_headers() , true));

fwrite($fp , print_r(  file_get_contents('php://input'), true));
fclose($fp);
?>
 
 

隨意發個訊息給群組,然後打開 line.txt,取得 LINE groupID,這是要給 Delphi7 程式用的。


1. 建立專案目錄

2. download SSL 下載  indy_OpenSSL096m.zip (經嘗試錯誤,要這個檔案才能用),解壓後,將 ssleay32.dll, libeay32.dll 放入專案的目錄內

3. FormidHTTP(Indy Clients 頁籤 )idSSLIOHandlerSocket (Indy I/O Handlers 頁籤) 2 個元件,這 2 個是主角;再放 Label(name=lblMessage)、Edit(name=txtMessage)、Memo、Button(name=btnLine),如下圖:




4. idHTTP 元件的 name 屬性改為 idHttpidSSLIOHandlerSocketname 屬性改為 IdSSL

5. IdSSLMethod 屬性改為 sslvSSLv23

6. idHttpIOHandler 屬性指向 IdSSL


 
...
...

procedure TfrmMain.FormCreate(Sender: TObject);
begin
    // 這段的目的是讓程式一啟動先載入 ssleay32.dll, libeay32.dll
    // 但第一次執行會出現錯誤
    // 所以用 try 把錯誤訊息壓下來不顯示
    // 
    // 2019.12.02
    // 無意間得知, 會發生這個現象是因為版本因素
    // 請自行上網尋找適合 INDY 的版本
    try
        idHttp.Get('https://隨便打個網址');    // 重點是網址要支援 https
    except

    end;
end;

...
...

procedure TfrmMain.btnLineClick(Sender: TObject);
const
    url = 'https://api.line.me/v2/bot/message/push';
var
    RequestBody: TStream;
    ResponseBody: string;
begin
    idHttp.Request.ContentType := 'application/json';
    idHttp.Request.CustomHeaders.Text := 'Authorization:Bearer  jj/dAZOPVE/blablabla很長的字串=';
    try
        try
            RequestBody := TStringStream.Create('{"to":"群組 ID", "messages": [{"type": "text","text": "'+UTF8Encode(txtMessage.Text)+'"}]}');    // UTF8Encode 很重要, 尤其訊息內有中文
            try
                ResponseBody := idHttp.Post(url, RequestBody);
                memo1.lines.add(ResponseBody);
            finally
                RequestBody.Free;
            end;
        except on E: EIdHTTPProtocolException do
                begin
                    memo1.Lines.Add(e.Message);
                    memo1.Lines.Add(e.ErrorMessage);
                end;
                on E: Exception do
                begin
                    memo1.Lines.Add(e.Message);
                end;
        end;
    finally
        
    end;
end;

 


這個程式有個小問題,就是第一次執行 idHttp 時會跳出錯誤訊息,所以才在 FormCreate

try

except

end;

將錯誤訊息欄截不顯示。如果看倌有更好的解法,也請您分享。謝謝啦~



2019.12.02
無意間得知, 會發生這個現象是因為版本因素
請自行上網尋找適合 INDY 的 ssleay32.dll, libeay32.dll 版本
您可以以 "Could not load SSL library" 查詢
會得到許多相關討論




2018-06-26

【Python】CentOS 7 使用 cx_Oracle 連線 Oracle 9i

參考資料 ----
Python Oracle drivers


Python 連線 Oracle 的驅動程式有很多種,因為 CentOS7 預設使用 cx_Oracle,並且其他驅動有涉及到版權規範,因此老人家還是選擇 cx_Oracle


* 以 root 身份登入 CentOS 7


* 到 Oracle 官網,(若無帳號就申請建立一個),登入、下載並安裝  oracle-instantclient11.2-basic.x86_64.rpm,   oracle-instantclient11.2-devel.x86_64.rpm, oracle-instantclient11.2-sqlplus.x86_64.rpm
因為 Oracle 官網常改版,所以自己要花點時間去摸索尋找。


* 新增並編輯 oracle.sh
[root]# vi  /etc/profile.d/oracle.sh
 
ORACLE_HOME="/usr/lib/oracle/11.2/client64"
PATH=$PATH:$ORACLE_HOME/bin
export PATH
LD_LIBRARY_PATH=$ORACLE_HOME/lib:/usr/lib:/usr/local/lib
export LD_LIBRARY_PATH
 
:x 存檔離開

重開機, 讓 oracle.sh 設定生效


新增 Python 的測試程式 oratest.py
內容如下:
 
# -*- coding: utf-8 -*-
import cx_Oracle
conn = cx_Oracle.connect("帳號/密碼@IP/資料庫名", encoding = "UTF-8", nencoding = "UTF-8")
curs = conn.cursor()
curs.execute('SELECT  *  FROM  資料表名')


# 原生方式是以欄位順序取得欄位值
# 例如 row[0], row[1], ...
# 我們要很明確知道 row[0], row[1] 各代表什麼欄位
# 可讀性不高

# 我們可以繞個彎
# 將資料集的欄位讀出並存入 list
columns = [i[0] for i in curs.description]

# 這裡要注意
# row 對欄位名並沒有大小寫分別
# 但因我們以 curs.description 讀出的欄位名是大寫
for row in curs:  
    print 'field1 = ' + row[columns.index('FIELD1')]

conn.close()
 


附帶看一下 curs.description 的內容:

[('FIELD1', , 2, 2, None, None, 0), ('FIELD2', , 8, 8, None, None, 0), ...]




要注意的是,若您的 Python 程式要放入排程跑,則要再加環境變數的設定:

[root]# vi /etc/crontab
 
SHELL=/bin/bash

# 設置 oracle 環境變數
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/lib/oracle/11.2/client64/bin
ORACLE_HOME="/usr/lib/oracle/11.2/client64"
LD_LIBRARY_PATH=/usr/lib/oracle/11.2/client64/lib:/usr/lib:/usr/local/lib

...
...

要執行的 python 程式
 
:x 存檔離開


不然您會發現,crontab 到指定時間有執行,資料卻沒存入 Oracle

2018-05-09

【jQuery】$.ajax() 在 smarty 內注意事項

當網頁套用 smarty 時,若有用到 $.ajax() 時,要注意程式碼格式
 
$.ajax({    // 左大括號之後必須空白
        url: 'index.php',    // 資料參數從這一列開始
        async: false,
        type: "POST",
        dataType: 'json',
        data: { ...
                ...
                'DATA': JSON.stringify(aData)
            },
        success: function(obj_inf)
                {
                    if(obj_inf.Code>0)
                        {
                            $('#form').submit();
                        }
                    else
                        {
                            alert(obj_inf.ResponseStr);
                        }
                }
        });
 

若緊接著寫會造成 smarty 編譯錯誤
 
// 這種寫法會造成 smarty 錯誤!!
$.ajax({url: 'index.php',
        async: false,
        type: "POST",
        dataType: 'json',
        data: { ...
                ...
                'DATA': JSON.stringify(aData)
            },
        success: function(obj_inf)
                {
                    if(obj_inf.Code>0)
                        {
                            $('#form').submit();
                        }
                    else
                        {
                            alert(obj_inf.ResponseStr);
                        }
                }
        });
 

錯誤訊息
 
Fatal error: Uncaught exception 'SmartyCompilerException' with message 'Syntax Error in template mysmarty.tpl" on line xxx "$.ajax({url: 'index.php'," - Unexpected ": ", expected one of: "}" , " " , 
 


2018-05-08

【Android Studio】標記書籤

Ctrl + F11,再按(主鍵盤區的) 數字,

例如:
Ctrl + F11  → 1,這樣就標記了 1 號書籤

當插入線(鍵盤游標) 在程式碼的其他地方,要回到 1 號書籤,則按

Ctrl + 1

2018-05-07

【Android Studio】執行時期為 APP 取得權限(授權)

參考資料 ----
Request App Permissions


在執行時期取得權限,是自 Marshmallow(6.0, API23) 加入的功能,為使用者提供更多保護;回想一下,您在安裝 APP 時,會仔細看 APP 要求取得哪些權限嗎?自 Android6.0(含) 起,在 APP 第一次執行時,會提示使用者,您的 APP 需要取得哪些權限,並必須得使用者允許。

所以在 Android6.0(含) 以上的裝置,如果沒有撰寫相關的程式碼,為 APP 執行時期取得權限,嚴重時甚至 APPcrash 掉!

AndroidManifest.xml (宣告 APP 必須取得的權限)
 
<!-- 要求取得相機權限 -->
<uses-permission android:name="android.permission.CAMERA"/>

...
...

<application
        ...
        ...
 

編輯 build.gradle(Module: app)
 
dependencies {
    
    ...
    ...

    implementation 'com.android.support:design:27.1.1'      // Snackbar 需要
}
 


activity_main.xml (記得要給 layout ID,不然 Snackbar 不會顯示)
 
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開啟相機"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:onClick="showCameraPreview"/>

</android.support.constraint.ConstraintLayout>
 

Mainactivity.java
 
public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback
{
    private static final int PERMISSION_REQUEST_CAMERA = 0;
    private View mLayout;

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

        mLayout = findViewById(R.id.main_layout);
    }

    public void showCameraPreview(View v)
    {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)
            {
                Snackbar.make(mLayout, "已取得相機權限, 開始預覽", Snackbar.LENGTH_SHORT).show();
                startCamera();
            }
        else
            {
                requestCameraPermission();
            }
    }

    private void startCamera()
    {
        // 開啟相機, 在此寫您自己的程式
    }

    private void requestCameraPermission()
    {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA))
            {
                Snackbar.make(mLayout, "需要取得相機權限", Snackbar.LENGTH_INDEFINITE)
                        .setAction("OK", new View.OnClickListener()
                                            {
                                                @Override
                                                public void onClick(View view)
                                                {
                                                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
                                                }
                                            })
                        .show();
            }
        else
            {
                Snackbar.make(mLayout, "無法開啟相機,可能有其他 APP 正在使用中 或 本 APP 尚未取得授權", Snackbar.LENGTH_SHORT).show();
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
            }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
    {
        if (requestCode == PERMISSION_REQUEST_CAMERA)
        {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
                {
                    Snackbar.make(mLayout, "已取得相機權限, 開始預覽", Snackbar.LENGTH_SHORT)
                            .show();
                    startCamera();
                }
            else
                {
                    Snackbar.make(mLayout, "請求相機權限被拒", Snackbar.LENGTH_SHORT)
                            .show();
                }
        }
    }
}
 


APP 安裝到實體裝置後,先關閉不執行,到 Android 的 "設定" 進一步觀察:

安裝在 KitKat4.4(API19),就已經取得權限



安裝在 Oreo8.0(API26),尚未取得權限



執行 APP,點擊 "開啟相機" 鈕,會出現請求授權的畫面



選取 "拒絕",就會跳出 "請求相機權限被拒" 的 Snackbar



再按一次 "開啟相機",這次 Snackbar 會提示您 APP 需要取得相機權限,選取 "OK"



打勾 "不要再詢問",並再次拒絕



這麼一來,就再也不會出現請求授權的畫面了,只能到 "設定" → 應用程式與通知應用程式資訊 → 找到 APP → 點擊 "權限" 去手動開啟授權

或是將 APP 移除再重新安裝了



若是在 請求授權畫面選擇 "允許" 或 手動進 "設定" 開啟授權 後,執行 APP 點擊 "開啟相機" 鈕,就會跳出已取得授權的 Snackbar




相關筆記 ----
Snackbar -- 比 Toast 更多功能的 跳出式訊息

2018-05-01

【VisualStudioCode】設定高亮度語法顯示 .tpl 檔

舊版 vscode 的畫面

功能表的 檔案
喜好設定
設定
→ 在畫面右半部  使用者設定 輸入下面內容

 
    "files.associations": 
    {
        "*.tpl": "html"
    }
 

再重新啟動 VisualStudioCode 就行了。


新版 vscode 的畫面

功能表的 檔案
→ 喜好設定
→ 設定
→ 在 【搜尋設定】欄輸入 files.associations 
→ 點擊【新增項目】鈕
→ 【索引鍵】欄輸入 *.tpl
→ 【值】欄輸入 html


2018-04-22

【Android Studio】Snackbar -- 比 Toast 更多功能的 跳出式訊息

參考資料 ----
Building and Displaying a Pop-Up Message


Snackbar 是自 API22 新加入的功能,類似 Toast,也是跳出式的訊息視窗 (是不是要取代 Toast? 官方沒有明確表示) 。


SnackbarCoordinatorLayout 內顯示時,還能有額外的特殊功能:

1. 使用者能以 左/右 滑動的方式移除 Snackbar

2. 如果在 Layout 內有諸如 FloatingActionButton 的元件時,當跳出 Snackbar 顯示,Snackbar 會將 FloatingActionButton 往上推;等 Snackbar 退出畫面,FloatingActionButton 會再回到原位。


如果您的畫面是 FramLayout,您甚至可以直接以 CoordinatorLayout 取代,就能充分運用 Snackbar 的功能。


先看傳統的程式使用 Snackbar 的方式



 build.gradle(Module: app)
 
...
...

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:design:27.1.1'      // 注意:要加上這行
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
 


activity_main.xml(採用 RelativeLayout)
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mv"    注意:Layout 要有 id
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Button"
        tools:layout_editor_absoluteX="16dp"
        tools:layout_editor_absoluteY="16dp"
        android:onClick="ShowSnackBar"/>

</RelativeLayout>
 


MainActivity.java
 
...
...

public void ShowSnackBar(View v)
{
    Snackbar.make(findViewById(R.id.mv), "顯示 Snackbar", Snackbar.LENGTH_SHORT)
            .show();
}
 





稍微修改 activity_mail.xml
 
<!-- 在 RelativeLayout 外層包上 CoordinatorLayout, 並微調 -->
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/mv"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<RelativeLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Button"
        tools:layout_editor_absoluteX="16dp"
        tools:layout_editor_absoluteY="16dp"
        android:onClick="ShowSnackBar"/>

</RelativeLayout>

</android.support.design.widget.CoordinatorLayout>
 



Snackbar 顯示時,往右滑移除。



Snackbar 還可以加入 文字按鈕,並在使用者點擊時執行指定的動作。
 
public void ShowSnackBar(View v)
{
    Snackbar.make(findViewById(R.id.mv), "顯示 Snackbar", Snackbar.LENGTH_LONG)
            .setAction("點擊執行", new View.OnClickListener()
                                    {
                                        @Override
                                        public void onClick(View v)
                                        {
                                            // 點擊訊息時要執行的動作
                                            Toast.makeText(MainActivity.this, "點擊了 Snackbar", Toast.LENGTH_SHORT).show();
                                        }
                                    })
            .setActionTextColor(Color.YELLOW)    // 還可以指定 文字顏色
            .show();
}