show code block

2017年9月24日 星期日

Android元件(WebView) — webView延伸,開啟FaceBook直播

前言:

之前發生一個問題,如果直接用WebView開啟Facebook的直播會出現一片空白的問題。

 因為要看直播一定要開啟facebook的頁面,他會要求你們開啟FB原生的app看直播



但點擊「開啟facebook」後連過去會是一片空白。
稍微檢查一下點連擊的url就會發現問題所在了。

他吐給你的url是一串意義不明的東西。


intent://video/?id=%7B25668421....

大致上長上面那樣

這時候我查了一下開啟fb的方式

發現他們開啟fb的暗碼都是 fb://xxxx

shouldOverrideUrlLoading撈到url後,直接把url內的intent改成fb就可以順利開啟直播囉!



重點程式碼:

下面例子的facebook直播網址請去臉書上面隨機找一個直播連結。

 WebViewClient mWebViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(TAG, "checkpoint  = " + url);

            if (url.contains("com.facebook.katana")) {
                try {
                    //確認是否有安裝facebook,假如這串錯誤 直接跳到catch
                    activity.getPackageManager().getPackageInfo("com.facebook.katana", 0);
                    //如果有抓取到url 把吐出來的intent改成fb
                    String facebookUrl = url.replace("intent", "fb");
                    //在外部開啟連結
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(facebookUrl)));
                    //在Log確認開啟連結的網址
                    Log.d("MainActivity", "facebookUrl = " + facebookUrl);
                } catch (PackageManager.NameNotFoundException e) {
                    Toast.makeText(activity, "你沒安裝facebook喔,請先安裝!", Toast.LENGTH_SHORT).show();
                }
                finish();
            } else {
                view.loadUrl(url);
            }

            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
            super.onReceivedError(view, request, error);
            Log.d(TAG, "error onReceivedError = " + request);
            view.setVisibility(View.GONE);
        }
    };
 

重點都在這了,下面就直接上完整程式碼。



完整程式碼:

activity_main.xml
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hello.kaiser.webview.MainActivity">

    <WebView
        android:id="@+id/webview_show"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>

 

MainActivity.java
下面的直播連結請去fb隨機找一個直播來取代
public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private WebView mWebView;
    private Activity activity;
    public static String FACEBOOK_URL = "https://www.facebook.com/hua.yingxin.9/videos/256684214854665/?hc_ref=ARTnenvMQbNXRjjvZag73twoaqxWfAd3a8D-U11DAVIaopXBFDEcHXMoGsG4KYL7BCQ&fref=gs&dti=1461364913895987&hc_location=group";


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

        mWebView = (WebView) findViewById(R.id.webview_show);
        WebSettings websettings = mWebView.getSettings();
        websettings.setSupportZoom(true);
        websettings.setJavaScriptEnabled(true);
        websettings.setAppCacheEnabled(true);
        websettings.setSaveFormData(true);
        websettings.setAllowFileAccess(true);
        websettings.setDomStorageEnabled(true);
        websettings.setBuiltInZoomControls(true);
        websettings.setDisplayZoomControls(false);
//        String ua = mWebView.getSettings().getUserAgentString();
//        mWebView.getSettings().setUserAgentString("WebView");

        websettings.setLayoutAlgorithm(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
                : WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
        websettings.setLoadWithOverviewMode(true);
        websettings.setUseWideViewPort(true);

        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
            websettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        }

        mWebView.setWebViewClient(mWebViewClient);
//        mWebView.setWebViewClient(new WebViewClient());
        mWebView.setWebChromeClient(new WebChromeClient());

        mWebView.loadUrl(FACEBOOK_URL);

    }

    WebViewClient mWebViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(TAG, "checkpoint  = " + url);

            if (url.contains("com.facebook.katana")) {
                try {
                    //確認是否有安裝facebook,假如這串錯誤 直接跳到catch
                    activity.getPackageManager().getPackageInfo("com.facebook.katana", 0);
                    //如果有抓取到url 把吐出來的intent改成fb
                    String facebookUrl = url.replace("intent", "fb");
                    //在外部開啟連結
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(facebookUrl)));
                    //在Log確認開啟連結的網址
                    Log.d("MainActivity", "facebookUrl = " + facebookUrl);
                } catch (PackageManager.NameNotFoundException e) {
                    Toast.makeText(activity, "你沒安裝facebook喔,請先安裝!", Toast.LENGTH_SHORT).show();
                }
                finish();
            } else {
                view.loadUrl(url);
            }

            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
            super.onReceivedError(view, request, error);
            Log.d(TAG, "error onReceivedError = " + request);
            view.setVisibility(View.GONE);
        }
    };
}
 



DEMO:

https://github.com/nikeru8/webviewConvertToFb






2017年9月22日 星期五

Android元件(CountDownTimer)– 倒數計時、ProgressBar、CountDownTimer整合

前言:

前陣子收到一個問題。
其實收到問題的當下就想到怎麼做了,順便實作一下吧!
其實這三個東西感覺自己都會,整合起來就會卡卡的,可能就像英文一樣吧XD
本篇就以CountDownTimer為主。

情境大致上是這樣 —
使用者按下Button然後跳出TimePicker,選好時間按確定之後,開始倒數。
在倒數的同時,ProgressBar會跟著百分比做改動。






重點程式碼:

會用到之前寫過的東西

ProgrssBar
http://nikeru8.blogspot.tw/2017/09/android-progressbar.html

時間選擇器
http://nikeru8.blogspot.tw/2016/12/androidtimepickerdialog.html

 CountDownTimer 這是官方文檔 —
https://developer.android.com/reference/android/os/CountDownTimer.html

CountDownTimer使用方法
new CountDownTimer(30000, 1000) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
  }.start();
 


上面這段直接copy官方文檔內的範例
可以看到CountDownTimer( 你要計時的毫秒 , 多少毫秒運行一次 )

上圖可以直接講完這method的所有使用方式。



完整程式碼:

activity_main.xml

 

 
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hello.kaiser.progressbardemo.MainActivity">

    <LinearLayout
        android:id="@+id/bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/progress_bar"
            style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"
            android:layout_weight="9" />

        <TextView
            android:id="@+id/progress_tv"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="5dp"
            android:layout_weight="1"
            android:text="0%" />

    </LinearLayout>


    <View
        android:id="@+id/line"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_below="@+id/bar_layout"
        android:layout_marginTop="20dp"
        android:background="#ffaa" />

    <TextView
        android:id="@+id/show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/line"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dp"
        android:text="time" />

    <Button
        android:id="@+id/show_picker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/show"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:onClick="timePickerBtn"
        android:text="picker" />

    <Button
        android:onClick="canelTimeBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/show_picker"
        android:layout_centerHorizontal="true"
        android:text="取消時間" />


</RelativeLayout>




在Java的部分,我連import都貼出來了,基本上照著做不會出問題。
在下方的code裡面我有下很多註解
如果還有不懂可以再提出
比較需要注意的事情是

1、SimpleDateFormat的使用,因為我只要抓取小時和分鐘,這邊只填上"HHmm"
2 、時間的相加減,其實都是用“毫秒”再作轉換
3、ProgressBar內的setMax和setProgress只能裝int,long轉換成int的方法我寫在下面





MainActivity.java
import android.app.TimePickerDialog;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TimePicker;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class MainActivity extends AppCompatActivity {


    private ProgressBar mProgressBar;
    private TextView mPercentage;

    private int progress = 0;//變化參數
    private int hours, mins;
    private Context mContext;
    private TextView mShow;
    private CountDownTimer countDownTimer;
    private Button show_picker;

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

    }

    private void initView() {
        show_picker = (Button) findViewById(R.id.show_picker);
        mShow = (TextView) findViewById(R.id.show);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        mPercentage = (TextView) findViewById(R.id.progress_tv);
    }


    //設定時間的按鈕
    public void timePickerBtn(View view) {
        //防呆
        show_picker.setClickable(true);
//        //假如重複選擇時間,暫停上一個計時器
        if (countDownTimer != null) {
            countDownTimer.cancel();
            countDownTimer.onFinish();
        }
        TimePickerDialog timepicker = new TimePickerDialog(mContext, onTimeSetListener, hours, mins, true);
        timepicker.show();
    }

    //取消倒數
    public void canelTimeBtn(View view) {
        show_picker.setClickable(true);
        mPercentage.setText("0%");
        mProgressBar.setProgress(0);
        progress = 0;
        if (countDownTimer != null) {
            countDownTimer.cancel();
            countDownTimer.onFinish();
        }
    }

    //TimePicker監聽
    TimePickerDialog.OnTimeSetListener onTimeSetListener = new TimePickerDialog.OnTimeSetListener() {

        @RequiresApi(api = Build.VERSION_CODES.N)
        @Override
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            //只format hours and mins
            SimpleDateFormat sdf = new SimpleDateFormat("HHmm");

            //抓取現在的時間
            Calendar calendar = Calendar.getInstance();
            int nowHours = calendar.get(Calendar.HOUR_OF_DAY);
            int nowMins = calendar.get(Calendar.MINUTE);

            Long time = null;
            //確認格式是我們在SimpleDateFormat內指定的樣子"HHmm"
            Log.d("checkpoint", " 確認選取的時間 = " + format(hourOfDay) + format(minute));
            Log.d("checkpoint", "卻線線在的時間 = " + format(nowHours) + format(nowMins));

            try {
                //選取時間 - 現在時間
                Date pickDate = sdf.parse(format(hourOfDay) + format(minute));
                Date nowDate = sdf.parse(format(nowHours) + format(nowMins));
                Long pickLongDate = pickDate.getTime();
                Long nowLongDate = nowDate.getTime();
                time = pickLongDate - nowLongDate;
                //設定ProgressBar
                mProgressBar.setMax(toIntExact(time));
                Log.d("checkpoint", "checkpoint = " + time);
            } catch (ParseException e) {
                e.printStackTrace();
                Log.d("checkpoint", "error - " + e);
            }

            if (time != null) {
                //time為毫秒 帶入倒數計時器CountDownTimer
                final Long finalTime = time;
                countDownTimer = new CountDownTimer(finalTime, 500) {
                    @Override
                    public void onFinish() {
                        mShow.setText("Done!");
                        if (progress != 0) {
                            mPercentage.setText("100%");
                            mProgressBar.setProgress(toIntExact(finalTime));
                        } else {
                            mPercentage.setText("0%");
                        }
                        show_picker.setClickable(true);
                    }

                    @Override
                    public void onTick(long millisUntilFinished) {
                        //這方法中間如果遇到個位數字,前方自動補0 "08"
                        NumberFormat f = new DecimalFormat("00");
                        long hour = (millisUntilFinished / 3600000) % 24;
                        long min = (millisUntilFinished / 60000) % 60;
                        long sec = (millisUntilFinished / 1000) % 60;

                        //換算必須用Double,如果使用int不管怎麼除都會是0
                        progress = (int) ((Double.valueOf(finalTime - millisUntilFinished) / Double.valueOf(finalTime)) * 100);
                        //在計時器內對ProgrssBar做每秒的更新
                        mProgressBar.setProgress(toIntExact(finalTime - millisUntilFinished));
                        //設定旁邊的文字%
                        mPercentage.setText(progress + "%");
                        //設定倒數計時
                        mShow.setText(f.format(hour) + ":" + f.format(min) + ":" + f.format(sec));
                    }
                }.start();
            }
        }
    };


    //重新把long變成String,中間如果遇到個位數字,前方自動補0 "08"
    private String format(long value) {
        String valueTwo = stringValue(value);
        if (valueTwo.length() == 1) {
            return "0" + valueTwo;
        }
        return valueTwo;
    }

    //轉換器long convert to String
    private String stringValue(long value) {
        return String.valueOf(value);
    }

    //轉換器 Long convert to int
    public static int toIntExact(long value) {
        if ((int) value != value) {
            throw new ArithmeticException("integer overflow");
        }
        return (int) value;
    }


}

 



歡迎提出討論喔!
DEMO:https://github.com/nikeru8/ProgressBarDemo/tree/IntegrationDemo



2017年9月19日 星期二

Android元件(ProgressBar) — ProgressBar進度條

前言:

製作ProgressBar

其實之前在

Android元件(DownloadManager) ─ downloadManager下載元件

就有使用過,這次把他拉出來另外做一篇。




重點程式碼:

先來看一下官方文檔ProgressBar

你要使用進度條,一定要知道三件事情。

一、你最大100%的比例為何(setMax)

二、現在跑到的比例在哪了(setProgress)

三、進度條的長相是水平的,還是圓形的.... 等等 (Style)
這邊是所有的Style,參考文檔可以去看看
Other progress bar styles provided by the system include:




實作:

activity_main.xml
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hello.kaiser.progressbardemo.MainActivity">

    <LinearLayout
        android:id="@+id/bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/progress_bar"
            style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="9" />

        <TextView
            android:id="@+id/progress_tv"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="5dp"
            android:layout_weight="1"
            android:text="0%" />

    </LinearLayout>

    <Button
        android:onClick="increasing"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/bar_layout"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp"
        android:text="增加10" />


</RelativeLayout>

 

我在activity_main.xml內多設置了一個按鈕來模擬增加的比率和多增一個TextView來觀測%數。

MainActivity.java
 
public class MainActivity extends AppCompatActivity {


    private ProgressBar mProgressBar;
    private TextView mPercentage;

    private int progress = 0;//變化參數

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

        initView();
        initSet();

    }

    private void initView() {
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        mPercentage = (TextView) findViewById(R.id.progress_tv);
    }

    private void initSet() {
        mProgressBar.setMax(100);
    }


    public void increasing(View view) {
        progress += 10;
        if (progress <= 100)
            mPercentage.setText(progress + "%");
        else
            Toast.makeText(this, "已經跑完囉", Toast.LENGTH_SHORT).show();

        mProgressBar.setProgress(progress);
    }
}




DEMO:

https://github.com/nikeru8/ProgressBarDemo

久違的github
為什麼之前不用呢?  這不好說XD

2017年9月12日 星期二

Android方法 — 逾時登出

前言:

App會了保護使用者,通常都會製作一個閒置太久就登出的功能。

通常會有兩個使用情境:
情境A、當使用者不使用app且把app縮到背景運行時,過一段時間後自動登出。
情境B、當使用者不使用手機,直接把手機螢幕黑掉待機。

逾時登出Demo


第一種比較簡單
 


此時你要有三個條件
1、非必要條件
如果你的app有常駐的ToolBar,你可以不用每個Activity都寫一次ToolBar
可以直接寫一個底層帶有ToolBar的Activity,並讓其他的Activity都extands(繼承)它。

為什麼呢?

因為如果你沒有寫這個class讓大家都繼承,你每一個Activity都要加上逾時登出的代碼。

這邊就不再實作繼承的BaseActivity,我會直接寫在範例內的Activity內。

2、情境A
在Application內複寫onTrimMemory的方法。
這裡是讓情境A在縮到背景運行時,瞬間記錄下時間點。
簡單的介紹一下客製化的Application
 概念:
android系統會為每個程序運行時創建一個Application類的對象且僅創建一個,所以Application可以說是單例 (singleton)模式的一個類.且application對象的生命週期是整個程序中最長的,它的生命週期就等於這個程序的生命週期。因為它是全局的單例的,所以在不同的Activity,Service中獲得的對象都是同一個對象。所以通過Application來進行一些,數據傳遞,數據共享,數據緩存等操作。

簡單的說就是存放整個App共用的共同參數的地方。


3、情境B
寫一個廣播BroadcastReceiver,在螢幕黑掉時,把暫停的時間點帶入廣播中,在喚起手機和app時,會再帶入。


edit(2017/10/20)
寫了一個part2
ndroid元件(ActivityLifecycleCallbacks) — 逾時登出製作 (二) 
http://nikeru8.blogspot.tw/2017/10/android.html

重點程式碼:

先來完成情境A的情況吧

首先你要先畫一個xml

activity_main.xml
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hello.kaiser.baseactivity.MainActivity">

    <TextView
        android:id="@+id/login_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="點我登入"
        android:textSize="50dp" />

</RelativeLayout>

 
此時我只有在中間佈局一個TextView,方便模擬登入登出效果
接下來來做自製的Application吧!
CustomApplication.java
public class CustomApplication extends Application {

    //用來判斷使用者登出與否
    public static boolean IsLogin = false; 

    @Override
    public void onCreate() {
        super.onCreate();
    }


}

 
裡面目前只塞一個boolean參數,用以判斷登入與否
/**
* true  登入狀態
* false 登出狀態
*/

此時Application是不會運行的!
你還要註冊自己的Application,告訴整個App,你要用我的喔!不可以使用系統預設的。
接下來把畫面切到AndroidManifest.xml中



 <application
        android:name=".CustomApplication"
        其他略...
    >  
    ...
    </application>
 



再來就是MainActivity.java
這邊稍微做麻煩一點
public class MainActivity extends AppCompatActivity {

    //元件
    private TextView mLoginButton;
    private Activity activity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activity = this;
        initView();//初始化 元件畫面
        initSet();
        initListener();//初始化 監聽功能
    }


    private void initView() {
        mLoginButton = (TextView) findViewById(R.id.login_btn);

    }

    private void initSet() {

    }

    private void initListener() {
        //設置按鈕點擊
        mLoginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //點擊後,把文字改成已登入/登出(模擬登入效果)
                if (!CustomApplication.IsLogin) {

                    mLoginButton.setText("已登入 \n 帳號:helloWorld");
                    CustomApplication.IsLogin = true; //告訴IsLogin你已經登入囉

                } else {

                    //出現Dialog和使用者確認是否要登出
                    new AlertDialog.Builder(activity)
                            .setMessage("確定要登出嗎?")
                            .setPositiveButton("ok", new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            mLoginButton.setText("點我登入");
                                            CustomApplication.IsLogin = false;//告訴IsLogin你已經登出囉
                                        }
                                    }
                            )
                            .setNegativeButton("no", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    dialog.dismiss();
                                }
                            }).show();


                }
            }
        });
    }
}

 
這邊我在程式碼內有做了AlertDialog詢問使用者是否登出。
這邊有一個CustomApplication.IsLogin的布林參數,用來判斷整個app的使用者登入的狀態。

簡單的模擬登入登出就完成囉。
正式開始實作(誤

情境A


先覆寫CustomApplication.java
public class CustomApplication extends Application {

    //用來判斷使用者登出與否
    public static boolean IsLogin = false;
    //螢幕消失要存的時間點
    public static long PAUSE_TIME = 0;
    //螢幕恢復時的時間點
    public static long RESTART_TIME = 0;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onTrimMemory(int level) {
        //螢幕背景運行時,記錄時間
        if (level == TRIM_MEMORY_UI_HIDDEN) {
            PAUSE_TIME = System.currentTimeMillis();
        }
        super.onTrimMemory(level);
    }
}

 
這邊在參數部分加入的暫停時間,並且在onTrimMemory內紀錄暫停的時間
TRIM_MEMORY_UI_HIDDEN 可參考這裡。
簡述就是他會在onStop前調用,那為什麼不直接用onStop呢?
因為如果寫在onStop內,你每個activity的轉換、關閉都會被調用。
onTrimMemory則是只有在畫面被轉到背景運行時才會被調用。


然後把畫面轉回MainActivity.java
public class MainActivity extends AppCompatActivity {

    //元件
    private TextView mLoginButton;
    private Activity activity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activity = this;
        initView();//初始化 元件畫面
        initSet();
        initListener();//初始化 監聽功能
    }


    private void initView() {
        mLoginButton = (TextView) findViewById(R.id.login_btn);

    }

    private void initSet() {

    }

    private void initListener() {
        //設置按鈕點擊
        mLoginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //點擊後,把文字改成已登入/登出(模擬登入效果)
                if (!CustomApplication.IsLogin) {

                    mLoginButton.setText("已登入 \n 帳號:helloWorld");
                    CustomApplication.IsLogin = true; //告訴IsLogin你已經登入囉

                } else {

                    //出現Dialog和使用者確認是否要登出
                    new AlertDialog.Builder(activity)
                            .setMessage("確定要登出嗎?")
                            .setPositiveButton("ok", new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            //系統登出
                                            memberLogOut();
                                        }
                                    }
                            )
                            .setNegativeButton("no", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    dialog.dismiss();
                                }
                            }).show();


                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();

        //假如登出狀態,設置所有時間歸零 參數
        boolean isMemberlogout = false;

        //如果喚醒時,暫停時間不為零
        if (CustomApplication.PAUSE_TIME != 0) {

            //獲取喚醒時的時間
            CustomApplication.RESTART_TIME = System.currentTimeMillis();

            //在這邊喚醒時間 - 暫停時間 每1000單位 = 1 second 這邊用三秒代替
            if ((CustomApplication.RESTART_TIME - CustomApplication.PAUSE_TIME) > Long.valueOf("3000")) {
                if (CustomApplication.IsLogin) {


                     //告訴它要變成登出狀態了
                    isMemberlogout = true;
                }
            }

            //告訴他要變成登出狀態了,請歸零時間
            if (isMemberlogout) {
                CustomApplication.PAUSE_TIME = 0;
                CustomApplication.RESTART_TIME = 0;
                memberLogOut();
            }
        }
    }

    //系統登出
    private void memberLogOut() {
        mLoginButton.setText("點我登入");
        //告訴IsLogin你已經登出囉
        CustomApplication.IsLogin = false;
    }
}

 
這裡因為登出元件重複到,所以額外拉出來當成一個方法,這樣可以減少籠code
memberLogOut
並且在onResume複寫,在這邊計算喚醒時間和暫停時間的秒數。
如果你要螢幕離開一秒後登出 單位為1000,看你自身需求。
這邊就完成簡單的情境A囉!
當你想切換到Line或是按到home鍵,只要過了三秒會自動幫你變成登出狀態。



情境B

寫一個廣播BroadcastReceiver,在螢幕黑掉時,把暫停的時間點帶入廣播中,在喚起手機和app時,會再帶入。

那就開始著手寫廣播吧

MyReceiver.java
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String actionStr = intent.getAction();
        //假如螢幕按掉了
        if (actionStr.equals(Intent.ACTION_SCREEN_OFF)) {
            //記錄時間點
            CustomApplication.PAUSE_TIME = System.currentTimeMillis();
        }
    }
}
 
這裏Intent有一個查看螢幕按掉的方法,直接調用,如果螢幕按掉就記錄時間。

之後再回到MainActivity.java寫一個MyReceiver的方法
 //如果螢幕黑頻按掉時,存取記錄時間點
    private void register() {

        IntentFilter fliter = new IntentFilter();
        fliter.addAction(Intent.ACTION_SCREEN_OFF);
        mReceiver = new MyReceiver();
        registerReceiver(mReceiver, fliter);
    }
 
上面這個方法可以在onCreate內調用它。

之後不要忘記在你離開App的同時,反註冊它。
@Override
    protected void onDestroy() {
        //在結束app的同時,關掉廣播
        if (mReceiver != null) {
            unregisterReceiver(mReceiver);
            mReceiver = null;
        }
        super.onDestroy();
    }
 

這時候試試看你的新功能吧!!

DEMO: https://github.com/nikeru8/timeoutLogout


這篇看完以興趣可以去看看
Android元件(ActivityLifecycleCallbacks) — 逾時登出製作 (二)




Android方法 — ScrollView內砍RecyclerView卡頓問題、資料顯示異常

前言:

最近的專題遇到這問題。
如果ScrollView內要顯示一個RecyclerView的話,因為兩個都有滾動效果。
滑起來就整個很卡、很慢、很鈍。
 就直接進入正題吧。








重點程式碼:


相信你也是因為遇到這個問題才進來的,我就不貼完整程式碼了。

1、滑動卡頓
因為兩個東西都會滑動,造成很卡,就乾脆把RecyclerView的滑動效果停掉吧!

  LinearLayoutManager llm = new LinearLayoutManager(mContext) {
            @Override
            public boolean canScrollVertically() {
                return false;
            }
        };
        llm.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(llm);
 

只要阻止RecyclerView滑動問題就迎刃而解了。

2、資料顯示異常

如果你查過其他資料,你會發現資料如果太大筆,RecyclerView的Item 顯示會怪怪的。

我沒遇到這問題(可能是塞的資料量太少),但如果你有使用ScrollView+RecyclerView這個組合的話,還是乖乖加上去吧。

就是在RecyclerView外面再包一層RelativeLayout 問題就解決囉。


協程(coroutine) - 協程為什麼要學它?

 Coroutine 協程 再強調一次 協程就是由kotlin官方所提供的線程api //Thread Thread { }.start() //Executor val execu...