[mobilesafe] 08_程序锁UI

Android 4.0

程序锁功能

步骤:1、未加锁
a) 设计布局,初始化组件
b) 获取数据(开启子线程获取)
c) 获取完毕,发送消息给主线程
d) 主线程接收到消息,为ListView设置适配器,更新界面数据
e) 设置条目点击事件,当被点击的时候,移除到集合外,添加到数据库中
f) 为每个移除的条目设置动画
2、加锁类似
技术点:
1、在未加锁,为每一个item设置一个点击事件,
当点击某个条目时,获取该条目信息,然后将其从未加锁List给移除掉,然后添加到加锁的List中去,
然后更新ListView界面内容
unLockedAdapter.notifyDataSetChanged();
lockedAdapter.notifyDataSetChanged();// 这句如果不加上,那么在已加锁列表不会添加上

2、在哪获取加锁和未加锁应用的个数?
在getCount()方面里面,因为getCount()最清楚ListView要被展示多少个条目。
@Override
public int getCount() {
    if (isAppUnlocked) {// 未加锁
        if (unlockedAppInfos != null) {
            tv_app_unlocked_count.setText("未加锁应用:"+unlockedAppInfos.size()+"个");
            //放这里最好,因为它最清楚ListView中有多少个条目
            return unlockedAppInfos.size();
        }
    } else {// 已加锁
        if (lockedAppInfos != null) {
            tv_app_locked_count.setText("已加锁应用:"+lockedAppInfos.size()+"个");
            //放这里最好,因为它最清楚ListView中有多少个条目
            return lockedAppInfos.size();
        }
    }
    return 0;
}

3、在未加锁界面,每次一点击一个条目,将该条目从未加锁集合中给移除,然后将其加入到数据库(保存加锁的应用的数据库),然后
notifyDataSetChanged(),更新ListView界面上的数据,加锁ListView和未加锁ListView都要更新,否则,不会同步数据。

4、播放动画,主要要等动画播放完毕,才能更新UI
1)播放xml定义的动画
a) 动画定义
<?xml version="1.0" encoding="utf-8"?>
<!-- 7、未加锁应用移到加锁应用对应的动画效果 -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="100%"
    android:toYDelta="0" >
</translate> 
b) 动画播放 
// a)添加到加锁应用对应的动画
Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.applock_add);
// 谁播放动画?当前被点击的item,也就是view
view.startAnimation(animation);
2)子线程中更新UI快捷操作
runOnUiThread(new Thread() {});
程序锁核心代码:
package cn.zengfansheng.mobilesafe;
 
import java.util.ArrayList;
import java.util.List;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import cn.zengfansheng.mobilesafe.db.dao.AppLockDao;
import cn.zengfansheng.mobilesafe.domain.AppInfo;
import cn.zengfansheng.mobilesafe.engine.AppInfoProvider;
import cn.zengfansheng.mobilesafe.utils.ToastUtils;
 
/**
* 18、程序锁功能
*
* @author hacket
*/

public class AppLockActivity extends Activity implements OnClickListener {
 
    private TextView tv_app_unlocked;
    private TextView tv_app_locked;
    private View ll_lv_unlocked;
    private View ll_lv_locked;
 
    private ListView lv_app_unlocked;// 未加锁ListView
    private ListView lv_app_locked;// 已经加锁ListView
 
    private TextView tv_app_unlocked_count;// 显示未加锁的应用的个数TextView
    private TextView tv_app_locked_count;// 显示加锁的应用的个数TextView
 
    private List<AppInfo> allAppInfos;//装有所有的应用的List集合
    private List<AppInfo> unlockedAppInfos;// 装有所有的未加锁的应用的List集合
    private List<AppInfo> lockedAppInfos;// 装有所有的加锁的应用的List集合
 
    private View ll_loading_data;// 获取数据的等待界面
 
    private AppLockAdapter unLockedAdapter;// 未加锁应用的适配器
    private AppLockAdapter lockedAdapter;// 已加锁应用的适配器
 
    private AppLockDao appLockDao;// 加锁应用数据库的dao
 
    private Handler handler = new Handler(){
 
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 数据获取完毕,发送消息通知主线程更新ui,将获取数据等待界面invisible
            ll_loading_data.setVisibility(View.INVISIBLE);
 
            // 设置适配器
            // a) 未加锁的适配器
            unLockedAdapter = new AppLockAdapter(true);
            lv_app_unlocked.setAdapter(unLockedAdapter);
 
            // b)已加锁的适配器
            lockedAdapter = new AppLockAdapter(false);
            lv_app_locked.setAdapter(lockedAdapter);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_app_lock);
 
        // a)初始化组件
        tv_app_unlocked = (TextView) this.findViewById(R.id.tv_app_unlocked);
        tv_app_locked = (TextView) this.findViewById(R.id.tv_app_locked);
        ll_lv_unlocked = this.findViewById(R.id.ll_lv_unlocked);
        ll_lv_locked = this.findViewById(R.id.ll_lv_locked);
 
        lv_app_unlocked = (ListView) this.findViewById(R.id.lv_app_unlocked);
        lv_app_locked = (ListView) this.findViewById(R.id.lv_app_locked);
 
        tv_app_unlocked_count = (TextView) this.findViewById(R.id.tv_app_unlocked_count);
        tv_app_locked_count = (TextView) this.findViewById(R.id.tv_app_locked_count);
 
        ll_loading_data = this.findViewById(R.id.ll_loading_data);
 
        appLockDao = new AppLockDao(getApplicationContext());
 
        // b)加锁应用和未加锁应用切换
        tv_app_unlocked.setOnClickListener(this);
        tv_app_locked.setOnClickListener(this);
 
        // c)获取数据
        fillData();
 
        // d)设置未加锁应用ListView中item的监听事件
        lv_app_unlocked.setOnItemClickListener(new OnItemClickListener() {
 
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    final int position, long id) {
 
                // a)添加到加锁应用对应的动画
                Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.applock_add);
                // 谁播放动画?当前被点击的item,也就是view
                view.startAnimation(animation);
 
                // b)在子线程中睡过动画播放的时间,然后在更新UI
                new Thread() {
                    @Override
                    public void run() {
                        // 不能主线程中睡眠,要开启子线程睡眠
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 睡了3秒后,然后在子线程中更新UI
                        runOnUiThread(new Thread() {
                            @Override
                            public void run() {
 
                                // 1、首先获取当前item的AppInfo信息
                                AppInfo appInfo = unlockedAppInfos.get(position);
                                // 2、 从unlockedAppInfos集合中移除该元素
                                unlockedAppInfos.remove(appInfo);
                                // 3、 从lockedAppInfos集合添加该元素
                                lockedAppInfos.add(appInfo);
                                // 4、并添加到加锁应用的数据库中去
                                appLockDao.add(appInfo.getAppPackageName());
 
                                // 4、更新界面
                                unLockedAdapter.notifyDataSetChanged();
                                lockedAdapter.notifyDataSetChanged();// 这句如果不加上,那么在已加锁列表不会添加上
                            }
                        });
                    }
                }.start();
            }
        });
        // e)设置加锁应用ListView中item的监听事件
        lv_app_locked.setOnItemClickListener(new OnItemClickListener() {
 
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    final int position, long id) {
 
                // 1)开启动画
                Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.applock_remove);
                view.startAnimation(animation);
 
                // 2)子线程中睡眠,等动画播放完毕,子线程中更新UI
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        runOnUiThread(new Thread() {
                            public void run() {
                                // 1、首先获取当前item的AppInfo信息
                                AppInfo appInfo = lockedAppInfos.get(position);
 
                                // 2、 从lockedAppInfos集合中移除该元素
                                lockedAppInfos.remove(appInfo);
                                // 3、 从unlockedAppInfos集合添加该元素
                                unlockedAppInfos.add(appInfo);
                                // 4、并添加到加锁应用的数据库中去
                                appLockDao.delete(appInfo.getAppPackageName());
 
                                // 4、更新界面
                                unLockedAdapter.notifyDataSetChanged();
                                lockedAdapter.notifyDataSetChanged();// 这句如果不加上,那么在已加锁列表不会添加上
                            }
                        });
                    }
                }.start();
            }
        });
    }
 
    /**
     * 4、填充ListView的数据
     */

    private void fillData() {
 
        // a) 首先将等待界面也invisible
        ll_loading_data.setVisibility(View.VISIBLE);
 
        // b) 开启子线程获取数据
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    ToastUtils.showToastInThread(AppLockActivity.this, "获取应用程序列表失败~~~");
                    return;
                }
                // 由于获取数据是一个耗时的操作,所以放在子线程中操作
                allAppInfos = AppInfoProvider.getAppInfos(getApplicationContext());
 
                unlockedAppInfos = new ArrayList<AppInfo>();
                lockedAppInfos = new ArrayList<AppInfo>();
 
                for (AppInfo appInfo : allAppInfos) {
                    if (appLockDao.find(appInfo.getAppPackageName())) {// 如果存在,为加锁应用
                        lockedAppInfos.add(appInfo);
                    } else {// 未加锁
                        unlockedAppInfos.add(appInfo);
                    }
                }
                handler.sendEmptyMessage(0);// 由于只有一个消息,可以发送一个空消息
            }
        }.start();
    }
 
    /**
     * 3、ListView优化用的ViewHolder
     * @author hacket
     */

    private static class ViewHolder {
 
        private ImageView iv_app_icon;// 应用图标icon
        private TextView tv_app_name;// 应用名称
        private ImageView iv_lock_status;// 是否加锁的图标
    }
    /**
     * 2、加锁和未加锁适配器
     * @author hacket
     */

    private class AppLockAdapter extends BaseAdapter{
 
        /**
         * 表示是否加锁,true表示未加锁,false表示已加锁
         */

        private boolean isAppUnlocked;
 
        public AppLockAdapter(boolean isAppUnlocked) {
            this.isAppUnlocked = isAppUnlocked;
        }
 
        @Override
        public int getCount() {
 
            if (isAppUnlocked) {// 未加锁
                if (unlockedAppInfos != null) {
                    tv_app_unlocked_count.setText("未加锁应用:"+unlockedAppInfos.size()+"个");//放这里最好,因为它最清楚ListView中有多少个条目
                    return unlockedAppInfos.size();
                }
            } else {// 已加锁
                if (lockedAppInfos != null) {
                    tv_app_locked_count.setText("已加锁应用:"+lockedAppInfos.size()+"个");//放这里最好,因为它最清楚ListView中有多少个条目
                    return lockedAppInfos.size();
                }
            }
            return 0;
        }
 
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
 
            View view;
            ViewHolder viewHolder;
            if (convertView != null && convertView instanceof LinearLayout) {
 
                view = convertView;
                viewHolder = (ViewHolder) view.getTag();
 
            } else {
 
                view = View.inflate(getApplicationContext(), R.layout.listview_app_locked, null);
 
                viewHolder = new ViewHolder();
                viewHolder.iv_app_icon = (ImageView) view.findViewById(R.id.iv_app_icon);
                viewHolder.tv_app_name = (TextView) view.findViewById(R.id.tv_app_name);
                viewHolder.iv_lock_status = (ImageView) view.findViewById(R.id.iv_lock_status);
 
                view.setTag(viewHolder);
            }
 
            if (isAppUnlocked) {// 未加锁
                AppInfo appInfo = unlockedAppInfos.get(position);
 
                viewHolder.iv_app_icon.setImageDrawable(appInfo.getAppIcon());
                viewHolder.tv_app_name.setText(appInfo.getAppName());
                viewHolder.iv_lock_status.setImageResource((R.drawable.wizard_lock));
 
            } else {// 已加锁
 
                AppInfo appInfo = lockedAppInfos.get(position);
 
                viewHolder.iv_app_icon.setImageDrawable(appInfo.getAppIcon());
                viewHolder.tv_app_name.setText(appInfo.getAppName());
                viewHolder.iv_lock_status.setImageResource((R.drawable.wizard_unlock));
 
            }
            return view;
        }
 
        @Override
        public Object getItem(int position) {
            return null;
        }
        @Override
        public long getItemId(int position) {
            return 0;
        }
    }
 
    // 1、加锁和未加锁切换的点击事件
    @Override
    public void onClick(View v) {
 
        switch (v.getId()) {
 
        case R.id.tv_app_unlocked:// 未加锁应用
 
            tv_app_unlocked.setBackgroundResource(R.drawable.tab_left_pressed);
            tv_app_locked.setBackgroundResource(R.drawable.tab_right_default);
            ll_lv_unlocked.setVisibility(View.VISIBLE);
            ll_lv_locked.setVisibility(View.INVISIBLE);
            break;
 
        case R.id.tv_app_locked:// 已加锁应用
 
            tv_app_unlocked.setBackgroundResource(R.drawable.tab_left_default);
            tv_app_locked.setBackgroundResource(R.drawable.tab_right_pressed);
            ll_lv_unlocked.setVisibility(View.INVISIBLE);
            ll_lv_locked.setVisibility(View.VISIBLE);
            break;
        }
    }
}

结果:
问题1:当退出,再次进来的时候,又恢复了原样,原因是存在内存,下次进来数据又丢失了。
解决:将数据存到数据库中,每次加载的时候,从数据库中获取数据,点击时添加和删除数据库中对应的记录。
问题2:播放动画时,有问题。
比如,未加锁应用移动应用到加锁中的动画,每次移除,都有下面的条目先移除,然后又回来了。
分析:由于播放动画是在主线程中单独开启一个新的子线程进行的播放,所以播放动画需要一定的时间。
当一个条目被移除时,而我们又复用了convertView,此动画还没有播放完毕,就会导致下一个条目播放这个动画,即被移除去,然后动画播放完毕,界面更新了,看似被移除的条目又被显示在界面上,这样看起来就是从右边移过来一样。
问题代码:
// d)设置未加锁应用ListView中item的监听事件
lv_app_unlocked.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view,
            int position, long id) {
        // 1、首先获取当前item的AppInfo信息
        AppInfo appInfo = unlockedAppInfos.get(position);

        //添加到加锁应用对应的动画
        Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.applock_add);
        // 谁播放动画?当前被点击的item,也就是view
        view.startAnimation(animation);
        
        // 2、 从unlockedAppInfos集合中移除该元素
        unlockedAppInfos.remove(appInfo);
        // 3、 从lockedAppInfos集合添加该元素
        lockedAppInfos.add(appInfo);
        // 4、并添加到加锁应用的数据库中去
        appLockDao.add(appInfo.getAppPackageName());
        // 4、更新界面
        unLockedAdapter.notifyDataSetChanged();
        lockedAdapter.notifyDataSetChanged();// 这句如果不加上,那么在已加锁列表不会添加上
    }
});
解决:我们知道,每一个操作系统,android,ios,wp,界面,都是一个死循环,内嵌一个消息机制,在不定时地等待消息的到来。
所以,我们要让动画有足够的时间播放完毕,然后才能更新ui的操作
解决后代码:
// d)设置未加锁应用ListView中item的监听事件
lv_app_unlocked.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view,
            final int position, long id) {
        // a)添加到加锁应用对应的动画
        Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.applock_add);
        // 谁播放动画?当前被点击的item,也就是view
        view.startAnimation(animation);
        
        // b)在子线程中睡过动画播放的时间,然后在更新UI
        new Thread() {
            @Override
            public void run() {
                // 不能主线程中睡眠,要开启子线程睡眠
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 睡了3秒后,然后在子线程中更新UI
                runOnUiThread(new Thread() {
                    @Override
                    public void run() {
                        // 1、首先获取当前item的AppInfo信息
                        AppInfo appInfo = unlockedAppInfos.get(position);
                        // 2、 从unlockedAppInfos集合中移除该元素
                        unlockedAppInfos.remove(appInfo);
                        // 3、 从lockedAppInfos集合添加该元素
                        lockedAppInfos.add(appInfo);
                        // 4、并添加到加锁应用的数据库中去
                        appLockDao.add(appInfo.getAppPackageName());
                        // 4、更新界面
                        unLockedAdapter.notifyDataSetChanged();
                        lockedAdapter.notifyDataSetChanged();// 这句如果不加上,那么在已加锁列表不会添加上
                    }
                });
            }
        }.start();
    }
});