360項目-12

## 電子狗1 ##java

- 關閉服務後中止循環 用一個變量控制 把true變成isRunandroid

        @Override
        public void onDestroy() {
            super.onDestroy();
            isRun = false;
        }    git

- 發送廣播,看門狗跳過檢測github

        認證成功後,發送廣播
        LockScreenActivity.java
    
        // 發送廣播,通知看門狗不要再攔截當前應用
        Intent intent = new Intent();
        intent.setAction(Constants.ACTION_UNLOCKPSW_APP);
        intent.putExtra("packageName", packageName);
        sendBroadcast(intent);
    
        -------------------------------------------
    
        Dog1Service.java
        class InnerReceiver extends BroadcastReceiver {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                // 看門狗獲得了消息,把獲得的包名添加到 已經解鎖的list裏
                unLockpackageName = intent.getStringExtra("packageName");
                unLockList.add(unLockpackageName);
            }
        }
    數據庫


            if (unLockList.contains(packageName)) {
                // 用過已經密碼解鎖了,需跳過驗證
                            continue;
            }canvas

- 相關優化api

        知識拓展:看門狗後臺一直在運行,這樣是比較耗電的。
        這裏監聽一下屏幕開啓和鎖屏的廣播
        關閉的時候 中止循環  而且清楚解鎖的list數據 開啓屏幕的時候  重新開始循環        緩存


        private BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (TextUtils.equals(action, Constants.ACTION_UNLOCKPSW_APP)) {
                    String packagename = intent.getStringExtra("packageName");
                    unlockList.add(packagename);
                } else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
                    startDog1();
                } else if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
                    unlockList.clear();
                    isRunning = false;
                }
            }
        };
    
- 利用activity啓動模式修復密碼輸入bug  啓動模式 重點重點重點重點重點重點重點重點重點重點重點重點安全

        1. 進入手機衛士,按home退到後臺,而後再打開加鎖app,進入後發現跳轉到手機衛士頁面
        2. 畫圖分析,正常狀況下的任務棧和bug時的任務棧圖;
        3. 解決問題;在功能清單文件LockScreenActivity加上字段
        <activity android:name="com.itheima.mobilesafe.LockScreenActivity" android:launchMode="singleInstance"/>app

- 隱藏最近打開的activity

        長按小房子鍵:彈出歷史記錄頁面,就會列出最近打開的Activity;
    
        1. 演示因爲最近打開的Activity致使的Bug;
        
        2. 容易暴露用戶的隱私
          最近打開的Activity,是爲了用戶能夠很快打開最近打開的應用而設計的;2.二、2.3普及後就把問題暴露出來了,很容易暴露用戶的隱私。好比你玩一些日本開發的遊戲:吹裙子、扒衣服這類遊戲。你正在玩這些有些,這個時候,爸媽或者大學女輔導員過來了,趕忙按小房子,打開背單詞的應用,這時大學女輔導員走過來講,幹嗎呢,把手機交出來,長按一下小房子鍵,這個時候很尷尬的事情就產生了。
        
            A:低版本是沒法移除的。低版本記錄最近8個;想要隱藏隱私,打開多個擠出去;
            B:4.0之後高版本就能夠直接移除了。考慮用戶呼聲比較高。
        
        3. 設置不在最近任務列表顯示activity
            <activity
                    android:excludeFromRecents="true"
                        android:name="com.itheima.mobilesafe.EnterPwdActivity"
                        android:launchMode="singleInstance" />
        
        4. 知識拓展,之後開發帶有隱私的軟件,或者軟件名稱很差聽的應用,就能夠加載在最近打開列表不包括字段.不少軟件都這麼作
        
        

- 加鎖優化
    
>頻繁調用數據庫 每次都要查詢
    
            從數據庫中讀取全部已加鎖應用列表,每次從集合中查詢判斷
    
            lockList = mDao.findAll();// 查詢全部已加鎖的應用列表
    
        // boolean isLcok = mDao.isLcok(packageName);
        boolean isLcok = lockList.contains(packageName);// 查看當前頁面是否在加鎖的數據庫中
    
- 這時候有問題 必選在加鎖和解鎖的時候去更新lockList裏的數據才行  監聽數據庫變化, 更新集合

    - 增長另一款軟件進入程序鎖。打開看看,是沒法打開輸入密碼頁面的;解析緣由;
        這個時候就須要根據數據庫的數據變化而改變集合的信息了,就用到了觀察者;

    - 監聽來電攔截時,監聽通話日誌變化的邏輯,就是這個原理

    - 具體實現

            AppLockDao.java

            // 數據庫改變後發送通知
            mContext.getContentResolver().notifyChange(
                    Uri.parse("content://com.itheima.mobilesafe/applockdb"), null);                    

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

            Dog1Service.java    

            // 監聽程序鎖數據庫內容變化
            getContentResolver().registerContentObserver(
                    Uri.parse("content://com.itheima.mobilesafe/applockdb"), true,
                    mObservable);        

            //服務結束時 註銷觀察者
            getContentResolver().unregisterContentObserver(mObservable);         

            /**
             * 觀察者對象
             */
            private ContentObserver mObservable = new ContentObserver(null) {
                @Override
                public void onChange(boolean selfChange) {
                    super.onChange(selfChange);
                    // 數據庫已經改變 從新查詢所有加鎖的數據
                    locklist = mDao.queryAll();
                }
            };

### AccessibilityService ###

> 系統輔助功能服務, 借用此服務能夠監聽頁面跳轉狀態

- 查看谷歌官方文檔 API Guide->User Interface->Accessibility->Building Accessibility Services

- 建立服務WatchDogService2繼承AccessibilityService

- 參照文檔在清單文件中配置服務

        <service
            android:name="com.itheima.mobilesafeteach.service.WatchDogService2"
            android:label="@string/accessibility_service_label"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>

            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config" />
        </service>


- 配置文件

        xml/accessibility_service_config.xml

        <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
            android:description="@string/accessibility_service_description"//服務描述文字
            android:packageNames="com.example.android.apis"//須要排除的應用包名(不須要配置)
            android:accessibilityEventTypes="typeAllMask"//監聽的事件,typeAllMask表示全部事件,此處不須要監聽全部事件, 只須要監聽窗口變化, 能夠傳typeWindowStateChanged
            android:accessibilityFlags="flagDefault"//須要額外信息時添加的標記(不須要配置)
            android:accessibilityFeedbackType="feedbackSpoken"//反饋類型,此處傳feedbackGeneric表示通用方式
            android:notificationTimeout="100"//通知超時時間(不須要配置)
            android:canRetrieveWindowContent="true"//是否須要獲取窗口頁面內容(不須要配置)
            android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"//配置修改輔助功能設置的的Activity頁面(不須要配置)
        />

- 直接運行項目

        運行成功以後, 進入設置->輔助功能->服務, 會發現多了手機衛士程序鎖的開關, 打開手機衛士程序鎖開關, 系統會自動啓動WatchDogService2的服務

- 重寫onAccessibilityEvent方法, 獲取窗口變化事件

        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
            //窗口狀態發生變化
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
                String packageName = event.getPackageName().toString();//當前窗口頁面所在的應用包名
                System.out.println("packageName:" + packageName);
            }
        }

- 複製Dog1Service中的邏輯到Dog2Service中

        去掉原來服務中的輪詢邏輯以及監聽屏幕開啓的廣播, 其他邏輯基本一致.

- 更新工具頁面開啓程序鎖服務2的開關

        boolean service2Running = ServiceStatusUtils.isServiceRunning(this,
                WatchDogService2.class);

        sivAppLock2.setToggleOn(service2Running);

- 程序鎖服務2的開關點擊後跳轉到輔助功能頁面

    - 導入設置源碼
    - 搜索"輔助功能"字符串
    - 查找設置的清單文件中有關輔助功能Activity的配置
    - 根據該配置使用隱式意圖跳轉到該頁面

            <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                  <action android:name="android.settings.ACCESSIBILITY_SETTINGS" />
                  <category android:name="android.intent.category.DEFAULT" />
                  <category android:name="android.intent.category.VOICE_LAUNCH" />
                  <category android:name="com.android.settings.SHORTCUT" />
             </intent-filter>

            //跳到輔助功能設置頁面
            Intent intent = new Intent(
                    "android.settings.ACCESSIBILITY_SETTINGS");
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            startActivity(intent);


## 流量統計 ##

- 鏈接真機,查看文件proc/uid_stat,發現該目錄下有不少以uid命名的文件夾
- 用戶id是安裝應用程序的時候操做系統賦給應用程序的

            下載:168251
            上傳:23544
                tcp_rcv :代碼下載的數據
                tcp_snd:表明上傳的數據

- 建立TrafficeManagerActivity
- 流量統計的api介紹

        TrafficStats.getMobileRxBytes();// 3g/2g下載總流量
        TrafficStats.getMobileTxBytes();// 3g/2g上傳總流量

        TrafficStats.getTotalRxBytes();// wifi+手機下載流量
        TrafficStats.getTotalTxBytes();// wifi+手機上傳總流量

        TrafficStats.getUidRxBytes(10085);// 某個應用下載流量
        TrafficStats.getUidTxBytes(10085);// 某個應用上傳流量

        這裏須要注意的是,經過 TrafficStats 獲取的數據在手機重啓的時候會被清空,因此,若是要對流量進行持續的統計須要將數據保存到數據庫中,在手機重啓時將數據讀出進行累加便可

- 在軟件管理,獲取每一個應用的uid(邏輯相似AppInfoProvider) ,展現一下
- 封裝TrafficInfo對象, 保存應用流量信息

        class TrafficInfo {
            public String packageName;
            public Drawable icon;
            public String name;
            public long rev;//接收流量
            public long send;//發送流量
        }

- 用asyncTask獲取app應用和流量信息   主要掌握  asyncTask如何使用 重點

        /**
         * 泛型1 運行 doInBackground的參數類型 execute的參數類型
         * 泛型2 進度  onProgressUpdate的參數類型  publishProgress的參數類型
         * 泛型3 結束    onPostExecute的參數類型  doInBackground的返回值類型
         *
         * @author Administrator
         *
         */
        private class TaTask extends AsyncTask<String, Integer, String> {
            // 執行前 UI線程 通常作一些初始化操做
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                mInfos = new ArrayList<TrafficInfo>();
            }
    
            // 子線程運行 任務
            @Override
            protected String doInBackground(String... params) {
                SystemClock.sleep(1500);
                // 參1 根據不一樣的flag獲取到的信息不一樣
                List<PackageInfo> installedPackages = packageManager
                        .getInstalledPackages(0);
                for (PackageInfo packageInfo : installedPackages) {
                    // packageInfo 對應manifest文件裏的信息
                    String packageName = packageInfo.packageName;// 包名
    
                    // applicationInfo對應application節點應用信息
                    ApplicationInfo applicationInfo = packageInfo.applicationInfo;
                    // 獲取應用圖標
                    Drawable icon = applicationInfo.loadIcon(packageManager);
                    // 應用名稱
                    String name = applicationInfo.loadLabel(packageManager)
                            .toString();
                    // 獲取流量信息
                    long rx = TrafficStats.getUidRxBytes(applicationInfo.uid);
                    long tx = TrafficStats.getUidTxBytes(applicationInfo.uid);
                    TrafficInfo info = new TrafficInfo(name, icon, tx, rx);
                    mInfos.add(info);
    
                    // publishProgress(30)//通知onProgressUpdate執行
                }
                return "123";
            }
    
            // doInBackground執行完以後執行 UI線程
            @Override
            protected void onPostExecute(String result) {
                super.onPostExecute(result);
                llLoading.setVisibility(View.INVISIBLE);// 隱藏進度條
                lvAt.setAdapter(new AmAdapter());
            }
    
            // 進度更新 UI線程
            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
            }
        }


- 開啓任務

        TaTask taTask = new TaTask();
        taTask.execute();// 執行任務

- 使用listview加載應用流量信息列表(item佈局和軟件管理相似)


## 手機殺毒 ##
- 建立AntiVirusActivity

- 圓形進度條

    - Github搜索Circle Progress, 找到lzyzsd/CircleProgress, 下載相關代碼
    - 簡單介紹CircleProgress的用法. Github上的源碼是Android Studio格式, 須要建立eclipse項目, 並拷貝相關代碼進行使用.
    - 導入CircleProgress庫, 將手機衛士項目和該庫關聯起來

            關聯        CircleProgress庫的時候有時候會關聯不上  從新解壓一份代碼 放到一個盤符下 最好路徑沒有中文    
            注意剛導入的工程 代碼在java文件夾裏 拷貝到src下  java裏的刪除掉


    - 佈局文件中配置圓形進度條    關於第三方控件的屬性名 不要去記憶 由於有demo能夠查看

             <LinearLayout
                android:id="@+id/ll_progress"
                android:layout_width="match_parent"
                android:layout_height="160dp"
                android:background="@color/global_blue"
                android:orientation="vertical" >
    
                <com.github.lzyzsd.circleprogress.ArcProgress
                    android:id="@+id/ap_progress"
                    android:layout_width="130dp"
                    android:layout_height="130dp"
                    android:layout_gravity="center_horizontal"
                    itheima:arc_bottom_text="掃描中"//底端文字描述
                    itheima:arc_bottom_text_size="16sp"//底端文字大小
                    itheima:arc_progress="0"//當前進度
                    itheima:arc_stroke_width="10dp"//進度條寬度
                    itheima:arc_suffix_text_padding="3dp"//中心文字右上角的後綴文字邊距(具體指百分號"%")
                    itheima:arc_suffix_text_size="22sp"//中心文字右上角的後綴文字大小(具體指百分號"%")
                    itheima:arc_text_color="#fff" //中心文字顏色
                />

                //掃描過程當中實時顯示的包名
                <TextView
                    android:id="@+id/tv_package_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginBottom="5dp"
                    android:singleLine="true"
                    android:text="包名"
                    android:textColor="#fff"
                    android:textSize="15sp" />
            </LinearLayout>


        PackageManager pm = getPackageManager();
        // 獲取全部已安裝/未安裝的包的安裝包信息
        //有一個flag是 GET_UNINSTALLED_PACKAGES表明已刪除,但還有安裝目錄的
        List<PackageInfo> packages = pm
                    .getInstalledPackages(PackageManager.GET_SIGNATURES);

        for (PackageInfo packageInfo : packages) {
            // 獲取簽名信息
                Signature[] signatures = packageInfo.signatures;
                //生成簽名信息的md5字符串
                String md5 = MD5Utils.encode(signatures[0].toCharsString());
                // 是否有病毒
                boolean isVirus = VirusDao.isVirus(getApplicationContext(), md5);
        }


- 掃描病毒數據庫的DAO

        AntiVirusDao.java

        /**
         * 病毒數據庫的封裝
         */
        public class AntiVirusDao {
        
            public static final String PATH = "data/data/com.itheima.mobilesafeteach/files/antivirus.db";
        
                /**
                 * 根據簽名的md5判斷是不是病毒
                 *
                 * @param md5
                 */
                public static boolean isVirus(String md5) {
                    SQLiteDatabase db = SQLiteDatabase.openDatabase(PATH, null,
                            SQLiteDatabase.OPEN_READONLY);
            
                    Cursor cursor = db.rawQuery(
                            "select count(*) from datable where md5=? ",
                            new String[] { md5 });
            
                    int count = 0;
                    if (cursor.moveToFirst()) {
                        count = cursor.getInt(0);
                    }
            
                    cursor.close();
                    db.close();
                    return count > 0;
                }

        }
- 掃描對象封裝

        class ScanInfo {
            public String packageName;
            public String name;
            public boolean isVirus;
            public Drawable icon;
        }

- 開始掃描

        /**
         * 開始掃描
         */
        private void startScan() {
            new ScanTask().execute();
        }
    
        class ScanTask extends AsyncTask<Void, VirusInfo, Void> {
            int virusCount = 0;//病毒數量
            @Override
            protected void onPreExecute() {
                mDatas = new ArrayList<VirusInfo>();
                //爲掃描列表設置數據
                mAdapter = new ScanAdapter();
                lvList.setAdapter(mAdapter);
                virusCount = 0;
            }
    
            @Override
            protected Void doInBackground(Void... params) {
                PackageManager pm = getPackageManager();
                // 獲取全部已安裝/未安裝的包的安裝包信息
                List<PackageInfo> packages = pm
                        .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
    
                for (PackageInfo packageInfo : packages) {
                    String name = packageInfo.applicationInfo.loadLabel(pm)
                            .toString();
                    Drawable icon = packageInfo.applicationInfo.loadIcon(pm);
    
                    // 獲取簽名信息
                    Signature[] signatures = packageInfo.signatures;
                    //生成簽名信息的md5字符串
                    String md5 = MD5Utils.encode(signatures[0].toCharsString());
                    // 是否有病毒
                    boolean isVirus = VirusDao.isVirus(getApplicationContext(), md5);
    
                    //初始化掃描對象
                    VirusInfo info = new ScanInfo();
                    info.name = name;
                    info.packageName = packageInfo.packageName;
                    info.icon = icon;
                    SystemClock.sleep(200);
    
                    //更新進度
                    publishProgress(info);
                }
    
                return null;
            }
    
            @Override
            protected void onProgressUpdate(VirusInfo... values) {

                // 獲取傳進來的數據
                VirusInfo info = values[0];
                // 添加到數據源
                if (info.isVirus) {
                    // 有毒 添加到數據源的第0個
                    mInfos.add(0, info);
                    // 病毒數量增長
                    virusCount++;
                } else {
                    mInfos.add(info);
                }

                //刷新listview
                mAdapter.notifyDataSetChanged();
    
                lvList.smoothScrollToPosition(mAdapter.getCount() - 1);//listview滑動到最後一個item的位置
            }
    
            @Override
            protected void onPostExecute(Void result) {
                lvList.smoothScrollToPosition(0);//listview從新滑動到頂部
            }
        }

 

    - 更新進度邏輯實現

        class ScanTask extends AsyncTask<Void, VirusInfo, Void> {
    
            private int totalNum;//帶掃描app總數
            private int progress;//掃描進度
    
            @Override
            protected Void doInBackground(Void... params) {
                PackageManager pm = getPackageManager();
                // 獲取全部已安裝安裝包信息
                List<PackageInfo> packages = pm
                        .getInstalledPackages(0);
    
                // 計算app總個數
                totalNum = packages.size();
    
                for (PackageInfo packageInfo : packages) {
                    
                    .......
    
                    //更新進度
                    publishProgress(info);
                }
    
                return null;
            }
    
            @Override
            protected void onProgressUpdate(VirusInfo... values) {
                progress++;//更新當前進度
                //更新進度
                apProgress.setProgress((int) (progress * 100 / totalNum + 0.5f));
                //更新包名
                tvPackageName.setText(values[0].packageName);
            }
        }

- 掃描結果

    - 佈局頁面(須要和掃描進度佈局一塊兒放在幀佈局FrameLayout中, 實現層疊展現)

             <LinearLayout
                android:id="@+id/ll_result"
                android:layout_width="match_parent"
                android:layout_height="160dp"
                android:background="@color/global_blue"
                android:gravity="center"
                android:orientation="vertical"
                android:visibility="gone" >
    
                <TextView
                    android:id="@+id/tv_result"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="您的手機很安全"
                    android:textColor="#fff"
                    android:textSize="18sp" >
                </TextView>
    
                <Button
                    android:id="@+id/btn_retry"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="5dp"
                    android:background="@drawable/btn_primary_selector"
                    android:paddingLeft="5dp"
                    android:paddingRight="5dp"
                    android:text="從新掃描"
                    android:onClick="startRetry"
                    android:textColor="#fff" >
                </Button>
            </LinearLayout>

    - 業務邏輯

            @Override
            protected void onPostExecute(Void result) {
                lvList.smoothScrollToPosition(0);//listview從新滑動到頂部
                
                llProgress.setVisibility(View.GONE);//隱藏進度佈局
                llResult.setVisibility(View.VISIBLE);//展現掃描結果界面
    
                if (virusCount ==0) {
                    tvResult.setText("您的手機很安全");
                } else {
                    tvResult.setText("您的手機很不安全");
                }
            }

- 製做病毒

        搞個apk,而後生成簽名,用簽名打包 看下md5信息 添加到數據庫就好

- 開門動畫  重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點


        @Override
        protected void onPostExecute(Void result) {
            llProgress.setDrawingCacheEnabled(true);//開啓繪製緩存,目的是爲了獲取最終繪製的圖片對象
            llProgress.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);//設置圖片質量
            Bitmap bitmap = llProgress.getDrawingCache();//獲取繪製的緩存的圖片對象
    
            //設置開門動畫左側圖片
            Bitmap leftBitmap = getLeftBitmap(bitmap);
            ivLeft.setImageBitmap(leftBitmap);
    
            //設置開門動畫右側圖片
            Bitmap rightBitmap = getRightBitmap(bitmap);
            ivRight.setImageBitmap(rightBitmap);
    
            showOpenAnimator();
        }

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

        /**
         * 獲取左邊圖片
         *
         * @param drawingCache
         * @return
         */
        public Bitmap getLeftBitmap(Bitmap drawingCache) {
            // 寬度是原圖一半
            int width = drawingCache.getWidth() / 2;
            int height = drawingCache.getHeight();
            // 生成目標圖片
            Bitmap destBitmap = Bitmap.createBitmap(width, height,
                    drawingCache.getConfig());
            // 建立一個畫布
            Canvas canvas = new Canvas(destBitmap);
    
            // 矩陣 能夠移動圖片
            Matrix matrix = new Matrix();
            Paint paint = new Paint();//畫筆
            // 把原圖畫到畫布上
            canvas.drawBitmap(drawingCache, matrix, paint);
    
            return destBitmap;
        }
    
        /**
         * 獲取右邊圖片
         *
         * @param drawingCache
         * @return
         */
        public Bitmap getRightBitmap(Bitmap drawingCache) {
            // 寬度是原圖一半
            int width = drawingCache.getWidth() / 2;
            int height = drawingCache.getHeight();
            // 生成目標圖片
            Bitmap destBitmap = Bitmap.createBitmap(width, height,
                    drawingCache.getConfig());
            // 建立一個畫布
            Canvas canvas = new Canvas(destBitmap);
    
            // 矩陣 能夠移動圖片
            Matrix matrix = new Matrix();
            // 把原圖往左移動一半的寬度
            matrix.setTranslate(-width, 0);
            // 畫筆
            Paint paint = new Paint();
            // 把原圖畫到畫布上
            canvas.drawBitmap(drawingCache, matrix, paint);
    
            return destBitmap;
        }

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

        /**
         * 展現開門動畫   getWidth
         */
        private void showOpenAnimator() {
            //1.左側圖片左移消失
            //2.右側圖片右移消失
            //3.左側圖片漸變消失
            //4.右側圖片漸變消失
            //5.結果界面漸變展現
            //ivLeft.setTranslationX(translationX)
            //ivLeft.setAlpha(alpha)

            AnimatorSet set = new AnimatorSet();
            set.playTogether(
                    ObjectAnimator.ofFloat(ivLeft, "translationX", 0,
                            -ivLeft.getWidth()),
                    ObjectAnimator.ofFloat(ivRight, "translationX", 0,
                            ivRight.getWidth()),
                    ObjectAnimator.ofFloat(ivLeft, "alpha", 1, 0),
                    ObjectAnimator.ofFloat(ivRight, "alpha", 1, 0),
                    ObjectAnimator.ofFloat(llResult, "alpha", 0, 1));
            set.setDuration(3000);//設置動畫時長
            set.start();//啓動動畫
        }
    注意上面代碼裏的 ivLeft.getWidth()也能夠換成bitmap的寬度

- 從新掃描

        /**
         * 從新掃描
         */
        public void startRetry(View view) {
            //1.左側圖片右移顯示
            //2.右側圖片左移顯示
            //3.左側圖片漸變顯示
            //4.右側圖片漸變顯示
            //5.結果界面漸變消失
            //ivLeft.setTranslationX(translationX)
            //ivLeft.setAlpha(alpha)
            btnRetry.setEnabled(false);//啓動動畫時, 禁用從新掃描按鈕
            AnimatorSet set = new AnimatorSet();
            set.playTogether(
                    ObjectAnimator.ofFloat(ivLeft, "translationX",
                            -ivLeft.getWidth(), 0),
                    ObjectAnimator.ofFloat(ivRight, "translationX",
                            ivRight.getWidth(), 0),
                    ObjectAnimator.ofFloat(ivLeft, "alpha", 0, 1),
                    ObjectAnimator.ofFloat(ivRight, "alpha", 0, 1),
                    ObjectAnimator.ofFloat(llResult, "alpha", 1, 0));
            set.setDuration(3000);
    
            set.addListener(new AnimatorListener() {
    
                @Override
                public void onAnimationStart(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    startScan();
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
            });
    
            set.start();
        }

- 細節處理

    - 中止掃描

            class ScanTask extends AsyncTask<Void, ScanInfo, Void> {
    
                private boolean isStop;//標記是否中止掃描
        
                @Override
                protected Void doInBackground(Void... params) {
        
                    for (PackageInfo packageInfo : packages) {
                        .......
                        if (isStop) {
                            break;
                        }
                    }
        
                    return null;
                }
        
                @Override
                protected void onProgressUpdate(ScanInfo... values) {
                    if (isStop) {
                        return;
                    }
                    ......
                }
        
                @Override
                protected void onPostExecute(Void result) {
                    if (isStop) {
                        return;
                    }
                    ......
                }
        
                public void stop() {
                    isStop = true;
                }
            }

            --------------------------------
            /**
             * 開始掃描
             */
            private void startScan() {
                //若是異步任務不爲空, 中止當前任務
                if (mTask != null) {
                    mTask.stop();
                    mTask = null;
                }
        
                //啓動新的異步任務
                mTask = new ScanTask();
                mTask.execute();
            }
        
            @Override
            protected void onPause() {
                super.onPause();
                //若是異步任務不爲空, 中止當前任務
                if (mTask != null) {
                    mTask.stop();
                    mTask = null;
                }
            }

    - 從新掃描按鈕啓用和禁用

            啓動開門動畫以後, 禁用從新掃描按鈕, 動畫結束以後, 啓用從新掃描按鈕

    - 處理橫豎屏切換                  fn+ctrl+f11 切換模擬器橫豎屏後, Activity的onCreate方法會重新走一次, 能夠經過清單文件配置,Activity強制顯示豎屏                  <activity                                 android:screenOrientation="portrait" />                  或者, 能夠顯示橫屏, 經過此配置能夠再也不執行oncreate方法 而是會執行onConfigurationChanged方法                  <activity                 android:configChanges="orientation|screenSize|keyboardHidden" />

相關文章
相關標籤/搜索