Android开发学习之路--Annotation注解简化view控件之初体验

一般我们在写android Activity的时候总是会在onCreate方法中加上setContentView方法来加载layout,通过findViewById来实现控件的绑定,每次写这么多代码总觉得很烦躁。近来看了下android中有Annotation来实现这方面的简化,对于java不是很了解,就简单的看了下。上次玩web的时候,springmvc也有很多的注解,不知道怎么实现的,这里其实基本上类似。

Annotation注解这里主要还是讲讲怎么使用吧,单纯的原理会把人绕进去的,没办法,java基础只能后面再补了,c搞久了,很多面向对象的思想只停留在大学的时候,除了linux内核的一些面向对象的思想。说了那么多的废话,接着继续我们的Annotation的学习吧,先新建工程emAnnotationStudy,新建EMLayoutBinder.java,代码如下:

package com.jared.emannotationstudy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by jared on 16/3/10.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EMLayoutBinder {
    int value();
}

这里的@Target,@Retention和@interface,先来简单的介绍下这几个内容吧。

@Target:说明了Annotation修饰的对象范围,Annotation可被用于packages、types等类,接口,枚举,Annotation类型;还可以是类成员方法,构造方法,成员变量,枚举值;方法参数和本地变量等。其一般有如下几种类型:

ElementType.CONSTRUCTOR:  构造器声明;

ElementType.FIELD:                    成员变量、对象、属性;

ElementType.LOCAL_VARIABLE:  局部变量声明;

ElementType.METHOD:                 方法声明;

ElementType.PACKAGE:                包声明;

ElementType.PARAMETER:           参数声明;

ElementType.TYPE:                        类、接口(包括注解类型)或enum声明;

这用到了TYPE。

@Retention:表示在什么级别保存该注解信息。其一般级别如下:

RetentionPolicy.SOURCE:    停留在java源文件,编译器被丢掉。

RetentionPolicy.CLASS:     停留在class文件中,但会被VM丢弃。

RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息。

这里给了最高级别RUNTIME。

@interface:这个就表示注解了,和interface很像,不过多了一个@符号。

int value():表示传入的参数是int类型的。

好了,既然定义好了那么怎么使用呢?单单一个注解怎么个搞搞?其实注解一般都是和java的反射原理一起使用的。还是简单学习下java的反射吧,网上资料很多,这里就简单理解理解了。在java中的反射机制,被称为Reflection,它允许运行中的java程序对自身进行检查,并能直接操作程序的内部属性或方法。

利用Reflection APIs可以获取任何已知名称的类的内部信息,包括package、type parameters、superclass、implemented
interfaces、inner classes、outer classes、fields constructors、methods、modifiers等。

Class:  
       表示某个具体的类或接口

Object:        每个类都使用Object 做为超类,所有对象都实现这个类的方法

Constructor:封装了Class的构造方法

Field:           提供有关类或接口的属性信息,以及对它的动态访问权限

Method:  
   提供类或者接口上的方法的信息

Modifier:     封装了Class(method、fields)的修饰域。

简单了解下java的发射机制,其实反射就是通过反向调用类的一些功能,可能会觉得很难理解,还是继续我们的学习吧,新建EMAnnotationParser类:

package com.jared.emannotationstudy;

import android.app.Activity;
import android.view.View;

import java.lang.reflect.Field;

/**
 * Created by jared on 16/3/10.
 */
public class EMAnnotationParser {
    public static void injectActivity(Activity activity) {
        if (null == activity) {
            return;
        }
        Class<Activity> activityClass = (Class<Activity>) activity.getClass();
        if (isEMLayoutBinder(activityClass)) {
            EMLayoutBinder layout = activityClass.getAnnotation(EMLayoutBinder.class);
            activity.setContentView(layout.value());
        }
        View decorView = activity.getWindow().getDecorView();
    }

    private static boolean isEMLayoutBinder(Class<?> c) {
        return c.isAnnotationPresent(EMLayoutBinder.class);
    }

这里实现了injectActivity的方法,通过getClass获取当前的Activity的class,然后通过isAnnotationPresent查看该Annotation,再通过getAnnotation获取该注解,接着就是把注解传入的那个layout通过activity的setContentView方法来加载到activity
中了。好了,那么我们来实现下MainActivity吧:

package com.jared.emannotationstudy;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

@EMLayout(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EMAnnotationParser.injectActivity(this);
        //setContentView(R.layout.activity_main);
    }
}

去掉了setContentView,直接一个@EMLayout就搞定了,是不是很方便,这里通过EMAnnotationParser的injectActivity方法。其实一般项目中会定义一个BaseActivity,MainActivity通过继承BaseActivity来实现,那样看上去会更加的清晰,那就实现下BaseActivity吧:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

/**
 * Created by jared on 16/3/10.
 */
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EMAnnotationParser.injectActivity(this);
    }
}

然后实现MainActivity继承BaseActivity:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

@EMLayoutBinder(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @EMViewBinder(R.id.hello)
    private TextView mHello;
    @EMViewBinder(R.id.test1)
    private Button mTest1;
    @EMViewBinder(R.id.test2)
    private Button mTest2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHello.setText("Hello Annotation!");
    }
}

运行后依然没有任何问题。既然layout通过注解了,那么控件也是可以通过注解的,接下去就去实现下了。首先新建Annotation为EMViewBinder:

package com.jared.emannotationstudy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by jared on 16/3/10.
 */

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EMViewBinder {
    int value();
}

这里就很好理解了,和LayoutBinder一样,只是target是FIFLD,因为是成员的变量。接着简单实现下反射:

package com.jared.emannotationstudy;

import android.app.Activity;
import android.view.View;

import java.lang.reflect.Field;

/**
 * Created by jared on 16/3/10.
 */
public class EMAnnotationParser {
    public static void injectActivity(Activity activity) {
        if (null == activity) {
            return;
        }
        Class<Activity> activityClass = (Class<Activity>) activity.getClass();
        if (isEMLayoutBinder(activityClass)) {
            EMLayoutBinder layout = activityClass.getAnnotation(EMLayoutBinder.class);
            activity.setContentView(layout.value());
        }
        View decorView = activity.getWindow().getDecorView();
        initViews(activityClass.getDeclaredFields(), decorView, activity);
    }

    private static boolean isEMLayoutBinder(Class<?> c) {
        return c.isAnnotationPresent(EMLayoutBinder.class);
    }

    private static boolean isEMViewBinder(Field filed) {
        return filed.isAnnotationPresent(EMViewBinder.class);
    }

    private static void initViews(Field[] fields, View view, Object object) {
        View view1;
        for (Field field : fields) {
            if(isEMViewBinder(field)) {
                EMViewBinder emView = field.getAnnotation(EMViewBinder.class);
                view1 = view.findViewById(emView.value());
                if(null != view1) {
                    try {
                        field.setAccessible(true);
                        field.set(object, view1);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

这里通过getDecorView来获取view,通过getDeclaredFields来获取Fields,然后通过getAnnotation来获取EMViewBinder注解,接着调用findViewById来找到这个控件。如果找到了,那么需要调用setAccessible为true,因为变量一般都是private的。大概的意思就这样了,下面我们修改下layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="10dp"
    tools:context="com.jared.emannotationstudy.MainActivity">

    <TextView
        android:id="@+id/hello"
        android:text="Hello World!"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_gravity="center"/>

    <Button
        android:id="@+id/test1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="test1"
        android:textAllCaps="false"/>

    <Button
        android:id="@+id/test2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="test2"
        android:textAllCaps="false"/>
</LinearLayout>

接着我们在MainActivity中添加代码如下:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

@EMLayoutBinder(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @EMViewBinder(R.id.hello)
    private TextView mHello;
    @EMViewBinder(R.id.test1)
    private Button mTest1;
    @EMViewBinder(R.id.test2)
    private Button mTest2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHello.setText("Hello Annotation!");
    }
}

运行看下效果如下:

完全达到了我们的预期,而且编写代码十分方便,不需要再引入一大堆的findViewById了。即使再添加更多的控件也轻松搞定。既然控件绑定好了,那么接下去还需要做的就是事件的绑定了。这里主要实现button的事件,新建EMOnClickBinder:

package com.jared.emannotationstudy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by jared on 16/3/10.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EMOnClickBinder {
    int[] value();
}

这里实现的是方法,所以使用了METHOD,而且button可能很多,所以用了int[] 数组。接着我们实现具体的反射:

private static boolean isEMOnClickBinder(Method method) {
        return method.isAnnotationPresent(EMOnClickBinder.class);
    }

    private static void initOnClick(Method[] allMethod, View root, Object object) {
        for (Method method : allMethod) {
            if (isEMOnClickBinder(method)) {
                EMOnClickBinder onClick = method.getAnnotation(EMOnClickBinder.class);
                MyOnClickListener click = new MyOnClickListener(method, object);
                int[] ids = onClick.value();
                for (int id : ids) {
                    root.findViewById(id).setOnClickListener(click);
                }
            }
        }
    }

    static class MyOnClickListener implements View.OnClickListener {
        private Method mMethod;
        private Object mReceiver;

        public MyOnClickListener(Method method, Object receiver) {
            mMethod = method;
            mReceiver = receiver;
        }

        @Override
        public void onClick(View v) {
            try {
                mMethod.setAccessible(true);
                mMethod.invoke(mReceiver, v);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

这里使用ID查找,调用setOnClickListener方法来注册方法,通过MyOnClickListener来实现具体的操作。当有事件触发的时候会调用onClick方法,进而调用method的invoke方法。就会调用到注解下的自定义方法了,这里传入的就是View。接着具体MainActivity的实现如下:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

@EMLayoutBinder(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @EMViewBinder(R.id.hello)
    private TextView mHello;
    @EMViewBinder(R.id.test1)
    private Button mTest1;
    @EMViewBinder(R.id.test2)
    private Button mTest2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHello.setText("Hello Annotation!");
    }

    @EMOnClickBinder({R.id.test1, R.id.test2})
    public void myOnClick(View view) {
        switch (view.getId()) {
            case R.id.test1:
                mHello.setText("I am test1");
                break;
            case R.id.test2:
                mHello.setText("I am test2");
            default:
                break;
        }
    }
}

是不是非常的简单清晰,以后把这几个文件当作工具,简单封装下,就可以不用每次写那么多的findViewById和setOnClickListener了。基本上Annotation就先学习到这里了。

时间: 2024-12-11 10:05:00

Android开发学习之路--Annotation注解简化view控件之初体验的相关文章

Android开发学习之路-让注解帮你简化代码,彻底抛弃findViewById

本文主要是记录注解的使用的学习笔记,如有错误请提出. 在通常的情况下,我们在Activity中有一个View,我们要获得这个View的实例是要通过findViewById这个方法,然后这个方法返回的是一个Object类型,我们还需要进行强制的类型转换,但是相信很多人都遇到过,当我们的一个布局中有很多个控件的时候,每一个控件都要进行上面的这个操作其实是很烦躁的,特别是强制类型转换,即使是用Alt+Enter,多按几次都累了.而今天要用的是通过注解的方式来简化这一个复杂的步骤,在我们编写好相应的代码

Android开发学习之路--网络编程之xml、json

一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载xampp,然后安装之类的就不再多讲了,参考http://cnbin.github.io/blog/2015/06/05/mac-an-zhuang-he-shi-yong-xampp/.安装好后,启动xampp,之后在浏览器输入localhost或者127.0.0.1就可以看到如下所示了: 这个就

android开发学习之路——连连看之游戏逻辑(五)

GameService组件则是整个游戏逻辑实现的核心,而且GameService是一个可以复用的业务逻辑类. (一)定义GameService组件接口 根据前面程序对GameService组件的依赖,程序需要GameService组件包含如下方法.   ·start():初始化游戏状态,开始游戏的方法.     ·Piece[][] getPieces():返回表示游戏状态的Piece[][]数组.     ·boolean hasPieces():判断Pieces[][]数组中是否还剩Piec

Android开发学习之路-RecyclerView滑动删除和拖动排序

Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开发学习之路-下拉刷新怎么做? 本篇是接着上面三篇之后的一个对RecyclerView的介绍,这里多说两句,如果你还在使用ListView的话,可以放弃掉ListView了.RecyclerView自动帮我们缓存Item视图(ViewHolder),允许我们自定义各种动作的动画和分割线,允许我们对It

android开发游记:meterial design 5.0 开源控件整套合集 及使用demo

android 的5.0发布不光google官方给出了一些新控件,同时还给出了一套符合material design风格的设计标准,这套标准将未来将覆盖google所有产品包括pc端,网站,移动端.在android端上陆续出现了许多开源的控件库开始以google的以 material design为指导而设计的新风格控件库,对比了多个库之后这里推荐一套比较齐全且效果比较好的控件库,使用方法和传统控件高度一致,并向下兼容,附上使用方式和demo下载. 效果图: 这是由rey5137发布的mater

Android开发学习之路-该怎么学Android(Service和Activity通信为例)

在大部分地方,比如书本或者学校和培训机构,教学Android的方式都基本类似,就是告诉先上原理方法,然后对着代码讲一下. 但是,这往往不是一个很好的方法,为什么? ① 学生要掌握这个方法的用途,只能通过记忆而不是理解 ② 当某些原理稍微复杂的时候,通过讲解是不能直接理解的,有时候下课回去了再看也不一定看得明白 ③ 对英语文档不够重视,有问题先百度 本鸟自学Android一年,慢慢也学习到了很多的方法,如果你也是一个入门不久但是觉得很多东西都不明白的新手,希望本文对你有帮助. 我觉得要想学好And

Android开发学习之路--Broadcast Receiver初体验

学习了Activity组件后,这里再学习下另一个组件Broadcast Receiver组件.这里学习下自定义的Broadcast Receiver.通过按键自己发送广播,然后自己接收广播.新建MyBroadcastReceiver,代码如下: package com.example.jared.broadcasttest; import android.content.BroadcastReceiver; import android.content.Context; import andro

Android开发学习之路-环境搭建

这里选择使用android studio 集成开发环境,因为as是google推出的单独针对android开发的环境,并且迭代周期很快,因此,肯定会替代eclipse成为andorid的开发环境.对于没有eclipse基础的我来说,可以直接从as开始学习. 搭建环境, 1. 下载as withiout SDK 2. 导入自己的SDK库 3. 这里要求必须联网,而且,必须是可以FQ的,要不然速度会很慢. 4.SDK manager 如果速度比较慢,可以打开option勾选force http选项,

Android开发学习之路-记一次CSDN公开课

今天的CSDN公开课Android事件处理重难点快速掌握中老师讲到一个概念我觉得不正确. 原话是这样的:点击事件可以通过事件监听和回调两种方法实现. 我一听到之后我的表情是这样的: 这跟我学的看的都不一样啊,这还分监听和回调.这个时候我立马提出问题,嗯,讲课老师看到了. 老师就说了:是不一样的,我们第四点(最后一点)会讲. 好,我立马坐好准备受教听了大半小时.听到最后我的表情是这样的: 废话说够了,开个小玩笑,不要见怪. 我们都知道,监听事件其实只有一种,就是回调.如果有人不明白什么是回调?可以