Crazy Android Note

Android事件处理

事件概述:

基于回调的事件处理
      主要做法就是重写Android组件特定的回调方法,或者重写Activity的回调方法。Android为绝大部分界面组件都提供了事件响应的回调方法。
 基于监听的事件处理
      主要做法就是为Android界面组件绑定特定的事件监听器
 一般来说,基于回调的事件处理可用于一些具有通用性的事件,基于回调的事件处理代码会显得比较简洁。但对于特定的事件处理,无法使用基于回调的事件处理,只能采用基于监听的事件处理。

1、基于监听的事件处理:

监听的处理模型:
    EventSource:事件源。事件发生的场所,通常就是各个组件,例如按钮,窗口、菜单等
    Event:事件。事件封装了界面组件上发生的特定事情(通常就是一次用户操作)。如果程序需要获得界面组件上多发生事件的相关信息,一般通过Event对象来取得。
    Event Listener:事件监听器。负责监听事件源所发生的事件,并对各种事件作出相应的相应。

    基于监听的事件处理模型,其中事件源最容易创建,任何界面组件都可以作为事件源;事件的产生无需程序员关心,它是由系统自动生成的;所以实现事件监听器是整个事件处理的核心

//全屏显示的设置
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindwo().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WidnowManager.LayoutParams.FLAG_FULLSCREEN);

//获取屏幕宽高
Display display = getWindowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
//屏幕宽高封装到metrics中了
display.getMetrics(metrics);

实际上可以把事件处理模型简化理解:当事件源组件上发生事件时,系统将会执行该事件源组件上监听器的对应处理方法。与普通Java方法调用不同的是,普通Java程序里的方法是由程序主动调用,事件处理中的事件处理器方法是有系统负责调用的。

所谓事件监听器其实就是实现了特定接口的Java类的实例。程序中实现事件监听器通常有几种形式(Button实现点击事件的几种方式):
    1、内部类形式:
    2、外部类形式:
    3、Activity本身作为事件监听器类:
    4、匿名内部类形式:

2、基于回调的事件处理:

基于监听的事件处理是一种委托式的事件处理,回调机制则恰恰相反,对于基于回调的事件处理模型,事件源和事件监听器是统一的,或者说事件监听器完全消失了。当用户在GUI组件上激发某个事件时,组件自己特定的方法将会负责处理该事件。

为了使用回调机制处理GUI组件上发生的事件,我们需要为该组件提供对应的事件处理方法——而Java又是一种静态语言,无法为某个对象动态的添加方法,因此只能继承GUI组件类,并重新该类的事件处理方法来实现。
为实现回调机制的事件处理,Android为所以的GUI组件都提供了一些事件处理的回调方法,一View为例:
    boolean onKeyDown(int keyCode, KeyEvent event)
    boolean onKeyLongPress(int keyCode, KeyEvent event)
    boolean onKeyShortcut(int keyCode, KeyEvent event)
    boolean onKeyUp(int keyCode, KeyEvent event)
    boolean onTouchEvent(MotionEvent event)
    boolean onTrackballEvent(MotionEvent event)

对于基于监听的事件处理模型,事件源和事件监听器是分离的,当事件源上发生特定事件时,该事件交给事件监听器负责处理;对于基于回调的事件处理模型,事件源和事件监听器是统一的,当事件源发生特定事件时,该事件还是由事件源本身负责处理(可通过自定义View,重写View的事件处理方法)。

基于回调的事件传播:
    几乎所有的基于回调的事件处理模型都有一个boolean类型的返回值,该返回值用于标识该处理方法是否能完全处理该事件。
        true:该处理方法已完全处理该事件,该事件不会传播出去
        false:该处理方法并未完全处理该事件,该事件会传播出去

    对于基于回调的事件传播而言,某组件上所发生的事件不仅会激发该组件上的回调方法,也会出发该组件所在的Activity的回调方法——只要事件能传播到该Activity。

相应系统设置的事件 Configuration

Configuration类专门用于描述手机设备上的配置信息,这些配置信息既包括用户特定的配置项,也包括系统的动态设备配置(屏幕的方向,键盘,信号的国家码,网络码等)
Configuration cfg = getResource().getConfiguration();

重写onConfigurationChanged方法响应系统设置更改
    如果需要监听系统配置的更改,则可以考虑重写Activity的onConfigurationChanged方法,该方法是一个基于回调的事件处理方法:当系统设置发生更改时,该方法会自动触发。
    为了让程序中动态地更改系统设置,我们可调用Activity的setRequestedOrientation(int)方法修改屏幕的方向:MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORINTATION_PORTRAIT);(将屏幕设置为竖屏)

Handler

处理线程间通讯

Handler、Looper、Message、MessageQueue

Message:Handler接收和处理的消息对象
Looper:每个线程只能拥有一个Looper。它的loop方法负责读取MessageQueue中的消息,读到消息后就把消息交给发送该消息的handler处理
MessageQueue:消息队列,它采用FIFO的方法管理Message。程序创建Looper对象时,它会在构造器中创建MessageQueue对象(从代码中观察,Looper为private,顾程序员无法通过构造器创建Looper)。
    private Looper(){
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

Handler:发送消息,处理消息。
    程序使用handler发送消息:与Handler发送的消息必须被送到指定的MessageQueue中;也就是说,如果希望Handler正常工作,必须在当前现场中有一个MessageQueue,否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前现场中有一个Looper对象。为保证当前线程中有Looper对象,可分为两种情况:
        在UI线程中:系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可以通过Handler处理消息
        在子线程中:必须自己创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法即可。prepare()方法保证每个线程最多只有一个Looper对象。

        Looper.prepare();
        /**   这中间的代码就是运行在新创建的Looper中   */
        Looper.loop();


        public static final void prepare(){
            if(sThreadLocal.get() != null){
                throw new RuntimeException("only one Looper may be created per thread")
            }
            sThreadLocal.set(new Looper());
        }

        接下来调用的Looper的静态方法loop()来启动它,loop()会使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler处理。
        loop()源码:

        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;

            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();

            for (;;) {  //使用一个死循环,不断的读取MessageQueue中的消息
                Message msg = queue.next(); // might block (获取消息队列中的下一个消息,如果没有消息,可能阻塞)
                if (msg == null) {
                    // No message indicates that the message queue is quitting.(如果消息为null,表面消息队列正在退出)
                    return;
                }

                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }

                msg.target.dispatchMessage(msg);

                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }

                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.  (使用final修饰该标示符,保证在分发消息的过程中线程标示符不会被修改)
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }

                msg.recycle();
            }
        }

AsyncTask(Params,Progress,Result)

异步任务,是一个抽象类,通常用于被继承,继承AsyncTask时需要指定三个泛型参数:
    Params:启动任务执行的输入参数类型,用于doInBackground(Params... params)方法的参数上(如果有),通过execute(Params)传入。
    Progress:后台任务执行过程中进度值的类型,用于onProgressUpdate(Progress... values)方法;
    Result:后台任务完成后返回结果的类型,用于onPostExecute(Result... result)方法;
每个AsyncTask只能被执行一次,多次执行会引发异常

Activity和Fragment

Activity在Manifest文件中的配置:
    exported参数:指定该Activity是否允许被其他应用调用;true表示可以被调用

一个Activity可以组合多个Fragment;反过来,一个Fragment也可以被多个Activity复用
Fragment可以响应自己的输入事件,并拥有自己的生命周期,但它们的生命周期直接被其所属的Activity的生命周期控制。例如:当Activity暂停时,该Activity内的所有Fragment都会暂停;当Activity被销毁时,该Activity内的所有Fragment都会被销毁。只有当该Activity处于活动状态时,程序员才可通过方法独立地操作Fragment。

通常创建Fragment需要实现如下三个方法:
    onCreate():系统创建Fragment对象后回调该方法,在实现代码中只初始化想要在Fragment中保持的必要组件,当Fragment被暂停或停止后可以恢复
    onCreateView():当Fragment绘制界面组件时会回调该方法。该方法必须返回一个View,该View也就是该Fragment所显示的View。
    onPause():当用户离开该Fragment时将会回调该方法。

在Activity中显示Fragment,需要将Fragment添加到Activity中。将Fragment添加带Activity有两种方式:
    1、在布局文件中使用元素添加Fragment,元素的android:name属性指定Fragment的实现类
    2、在Java代码中通过FragmentTransaction对象的add()方法来添加Fragment。

Activity和Fragment相互传递数据:
    Activity向Fragment传递数据:在Activity中创建Bundle数据包,并调用Fragment的setArgument(Bundle bundle)方法即可将Bundle数据包传递给Fragment.Fragment通过getArgument()方法获取从Activity传递过来的Bundle数据包
    Fragment向Activity传递数据或Activity需要在Fragment运行中进行实时通信:在Fragment中定义一个内部调用接口,再让包含该Fragment的Activity实现该回调接口,这样Fragment即可调用该回调方法将数据传递给Activity。

Fragment管理与Fragment事务
    管理Fragment主要依靠FragmentManager;FragmentManager可以完成以下几个功能:
        使用findFragmeById()或findFragmentByTag();方法获取指定的Fragment
        使用popBackStack()方法将Fragment从后台栈中弹出(模拟用户按下BACK键)
        调用addOnBackStackChangeListener()注册一个监听器,用于监听后台栈的变化

    如果需要添加、删除、替换Fragment,则需要借助于FragmentTransaction对象,它代表Activity对Fragment执行的多个改变

开发兼顾屏幕分辨率的应用
    为了开发兼顾屏幕分辨率的应用,可以考虑在res/目录下为大屏幕、600dpi的屏幕建立相应的资源文件夹:values-large、values-sw600dp;

Fragment生命周期:
    onAttach():当该Fragment被添加到Activity时被调用。该方法只会被调用一次
    onCreate(Bundle savedStatus):创建Fragment时被调用,该方法只会被调用一次
    onCreateView():每次创建、绘制该Fragment的View组件时回调该方法,Fragment将会显示该方法返回的View组件
    onActivityCreated():当Fragment所在的Activity被启动完成后回调该方法
    onStart():启动Fragment时被调用
    onResume():恢复Fragment时被回调,在onStart()方法后一定会回调该方法
    onPause():暂停Fragment时被回调
    onStop():停止Fragment时被回调
    onDestroyView():销毁该Fragment所包含的View组件时回调
    onDetach():将该Fragment从Activity中删除、替换完成时回调该方法,在onDestroy()方法后一定会回调onDetach()方法。该方法只会被调用一次

    最常重写的是onCreateView()方法--该方法返回的View是需要在Fragment中显示的View

Intent和IntentFilter

ComponentName

Action、Category属性与intent-filter配置
    Intent的Action、Category属性都是一个普通的字符串。其中Action代表该Intent所要完成的一个抽象“动作”,而Category则用于为Action增加额外的附加类别信息。通常两者配合使用。

    Intent intent = new Intent();
    intent.setAction(MainActivity.CRAZYIN_ACTION);//这里的MainActivity.CRAZYIN_ACTION就是一个字符串
    startActivity(intent);

    以上为隐式Intent调用,这里只能启动Activity配置中配置了该action的Activity。
        

    元素通常包含如下子元素:
        0 ~ N个子元素
        0 ~ N个子元素
        0 ~ 1个子元素
    一个Intent对象最多只能包括一个Action属性,程序可调用Intent的setAction(String str)方法来设置Action属性;但一个Intent对象可以包括多个Category属性,程序可以通过调用Intent的addCategory(String str)方法为Intent添加Category属性。当程序创建Intent时,该Intent默认启动Category属性值为Intent.CATOGORY_DEFAULT常量的组件。

指定Action、Category调用系统Activity
    给Intent设置对应的action,隐式调用系统Activity

    点击按钮,返回系统Home桌面:
        intent.setAction(Intent.ACTION_MAIN);
        intent.addCategoty(Intent.CATEGORY_HOME);

Data、Type属性与intent-filter配置
    Data属性通常用于向Action属性提供操作的数据。Data属性接受一个Uri对象,一个Uri对象通常通过如下形式的字符串表示:
        content://com.android.contacts/contacts/1
        tel:123
        Uri字符串总满足如下格式:
        scheme://host:port/path
        例子中content://com.android.contacts/contacts/1,其中content是scheme部分,com.android.contacts是host部分,Port部分省略了,/contacts/1是path部分

    Type属性用于指定该Data属性所指定Uri对应的MIME类型,这种MIME类型可以是任何自定义的MIME类型,只要符合abc/xyz格式的字符串即可。
        如果Intent先设置了Data属性,后设置Type属性,那么Type属性将会覆盖Data属性。
        如果Intent先设置了Type属性,后设置Data属性,那么Data属性将会覆盖Type属性。
        如果希望既有Data属性,也有Type属性,则应该调用Intent的setDataAndType()方法

        在Manifest中,声明Data,Type是通过元素:
            

资源文件

assets目录存放的资源代表应用无法直接访问的原生资源,应用程序需要通过AssetsManager以二进制流的形式来读取资源,而res目录下的资源,Android SDK会在编译该应用时,自动在R.java文件中为这些资源创建索引,程序可直接通过R资源清单类进行访问。

/res/raw/文件夹下存放任意类型的原生资源(比如音频文件、视频文件等)。在java代码中可通过调用Resource对象的openRawResource(int id)方法来获取该资源的二进制输入流。实际上,如果应用程序需要原生资源,也可以吧这些原生资源保存到/assets/目录下,然后在应用程序中使用AssetsManager来访问这些资源

*res/raw和assets的相同点:
1.两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。

*res/raw和assets的不同点:
1.res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。
2.res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹

*读取文件资源:
1.读取res/raw下的文件资源,通过以下方式获取输入流来进行写操作
· InputStream is =getResources().openRawResource(R.id.filename);
2.读取assets下的文件资源,通过以下方式获取输入流来进行写操作
· AssetManager am = null;
· am = getAssets();
· InputStream is = am.open(“filename”);

注意1:Google的Android系统处理Assert有个bug,在AssertManager中不能处理单个超过1MB的文件,不然会报异常,raw没这个限制可以放个4MB的Mp3文件没问题。(带验证)

注意2:assets 文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像 xml, java 文件被预编译,可以存放一些图片,html,js, css 等文件。(带验证)

ClipDrawable:代表从其他位图上截取的一个“图片片段”,通过ClipDrawable对象的setLevel(int level)控制截取图片的部分,level为0时,截取的图片片段为空,level为10000时,截取整张图片

主题和样式的区别:
主题不能作用于单个View组件,主题应该对整个应用中的所有Activity起作用,或对指定的Activity起作用
主题定义的格式应该是改变窗口外观的样式,例如窗口标题、窗口边框等。
使用定义好的主题
1、在代码中:在setContentView()之前执行,setTheme(R.style.CrazyTheme);
2、在Manifest中:在

你可能感兴趣的:(android,Note)