这里我带你们来学习一下注解 而且用来写下一个模仿xUtils3 中View框架
此框架 能够省略activity或者fragment的 findViewById 或者设置点击事件的烦恼
我正参加2016CSDN博客之星的比赛 但愿您能投下宝贵的一票,点击进入投票
个人github上的源码,包含doc和使用说明html
以下代码:java
package a.fmy.com.myapplication;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import a.fmy.com.mylibrary.FmyClickView;
import a.fmy.com.mylibrary.FmyContentView;
import a.fmy.com.mylibrary.FmyViewInject;
import a.fmy.com.mylibrary.FmyViewView;
//你的fragment的布局id Your fragment's LayoutId
@FmyContentView(R.layout.fragment_blank)
public class BlankFragment extends Fragment {
//你想实例化控件的id
//Do you want to control instance id
// 等价于 findViewByid
//Equivalent to the findViewByid
@FmyViewView(R.id.tv1)
TextView tv1;
@FmyViewView(R.id.tv2)
TextView tv2;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//初始化fragment Initialize Fragement
return FmyViewInject.injectfragment(this,inflater,container);
}
//你想给哪一个控件添加 添加事件 的id
//Do you want to add add event id to which controls
@FmyClickView({R.id.tv1,R.id.tv2})
public void myOnclick(View view){
switch (view.getId()) {
case R.id.tv1:
tv1.setText("TV1 "+Math.random()*100);
break;
case R.id.tv2:
tv2.setText("TV2 "+Math.random()*100);
break;
default:
}
}
}
package a.fmy.com.myapplication;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.widget.FrameLayout;
import a.fmy.com.mylibrary.FmyContentView;
import a.fmy.com.mylibrary.FmyViewInject;
import a.fmy.com.mylibrary.FmyViewView;
@FmyContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@FmyViewView(R.id.fl)
FrameLayout fl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initActivity
// 初始化activity
FmyViewInject.inject(this);
}
@Override
protected void onResume() {
super.onResume();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fl,new BlankFragment());
fragmentTransaction.commit();
}
}
java注解教学你们点击进入大体的看一下便可 否则我不知道这篇博客须要写多久android
这里咱们先写一个用于activity框架 你学习完了以后其实你也会fragment了.
1. 实现activity不须要调用setContentView(R.layout.activity_main);此方法完成布局填充 咱们看下效果
不使用框架:git
package a.fmy.com.mylibrary;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
使用框架:github
package a.fmy.com.mylibrary;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
@FmyContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FmyViewInject.inject(this);
}
}
第一步:
建立一个注解类以下
@Target —>>此注解在什么地方可使用 如类仍是变量
ElementType.TYPE只能在类中使用此注解
@Retention(RetentionPolicy.RUNTIME) 注解能够在运行时经过反射获取一些信息(这里若是你疑惑那么请六个悬念继续向下看)markdown
/** * 此方注解写于activity类上 能够免去 setContentView()步骤 * @author 范明毅 * @version 1.0 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FmyContentView {
/** * 保存布局文件的id eg:R.layout.main * @return 返回 布局id */
int value();
}
第二步:
写一个工具类 配合注解使用 当开发者使用此类时激活注解的做用app
public class FmyViewInject {
/** * 保存传入的activity */
private static Class<?> activityClass;
/** * 初始化activity和全部注解 * * @param obj * 你须要初始化的activity */
public static void inject(Object obj) {
}
/** * 初始化activity布局文件 让其不用调用setContentView * * @param activity */
private static void injectContent(Object obj) {
}
}
你们先不用着急看不懂为何这样写缘由框架
核心源码位于injectContent 咱们来实现此方法dom
/** * 初始化activity布局文件 让其不用调用setContentView * * @param activity */
private static void injectContent(Object obj) {
// 获取注解
FmyContentView annotation = activityClass
.getAnnotation(FmyContentView.class);
if (annotation != null) {
// 获取注解中的对应的布局id 由于注解只有个方法 因此@XXX(YYY)时会自动赋值给注解类惟一的方法
int id = annotation.value();
try {
// 获得activity中的方法 第一个参数为方法名 第二个为可变参数 类型为 参数类型的字节码
Method method = activityClass.getMethod("setContentView",
int.class);
// 调用方法 第一个参数为哪一个实例去掉用 第二个参数为 参数
method.invoke(obj, id);
} catch (Exception e) {
e.printStackTrace();
}
}
此方法写完后工具类的inject()方法调用便可ide
/** * 初始化activity和全部注解 * * @param obj * 你须要初始化的activity */
public static void inject(Object obj) {
activityClass = obj.getClass();
// 初始化activity布局文件
injectContent(obj);
}
完整代码:
public class FmyViewInject {
/** * 保存传入的activity */
private static Class<?> activityClass;
/** * 初始化activity和全部注解 * * @param obj * 你须要初始化的activity */
public static void inject(Object obj) {
activityClass = obj.getClass();
// 初始化activity布局文件
injectContent(obj);
}
/** * 初始化activity布局文件 让其不用调用setContentView * * @param activity */
private static void injectContent(Object obj) {
// 获取注解
FmyContentView annotation = activityClass
.getAnnotation(FmyContentView.class);
if (annotation != null) {
// 获取注解中的对应的布局id 由于注解只有个方法 因此@XXX(YYY)时会自动赋值给注解类惟一的方法
int id = annotation.value();
try {
// 获得activity中的方法 第一个参数为方法名 第二个为可变参数 类型为 参数类型的字节码
Method method = activityClass.getMethod("setContentView",
int.class);
// 调用方法 第一个参数为哪一个实例去掉用 第二个参数为 参数
method.invoke(obj, id);
} catch (Exception e) {
e.printStackTrace();
}
}
}
赶快去试试 咱们继续写下一步 用法在开始的示例有
效果以下
@FmyContentView(R.layout.activity_main)
public class MainActivity extends FragmentActivity {
//直接实例化
@FmyViewView(R.id.fl)
private FrameLayout fl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FmyViewInject.inject(this);
}
}
第一步:
继续写一个注解
/** * 此方注解写于activity类中 控件变量上 能够省去findViewId 的烦恼 * @author 范明毅 * @version 1.0 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FmyViewView {
/** * 保存view控件的id * @return view控件id */
int value();
}
第二步 继续第一节的”activity设置填充布局框架”中的工具类添加新的方法
/** * 初始化activity中的全部view控件 让其不用一个findViewid 实例化 * * @param activity */
private static void injectView(Object activityOrFragment) {
// 对象全部的属性
Field[] declaredFields = null;
// 健壮性
if (activityClass != null) {
// 获取du全部的属性 包含私有 保护 默认 共开 但不包含继承等
// getFields能够获取到全部公开的包括继承的 但没法获取到私有的属性
declaredFields = activityClass.getDeclaredFields();
}
// 健壮性
if (declaredFields != null) {
// 遍历全部的属性变量
for (Field field : declaredFields) {
// 获取属性变量上的注解
FmyViewView annotation = field.getAnnotation(FmyViewView.class);
// 若是此属性变量 包含FMYViewView
if (annotation != null) {
// 获取属性id值
int id = annotation.value();
Object obj = null;
try {
// 获取activity中方法
obj = activityClass.getMethod("findViewById",
int.class).invoke(activityOrFragment, id);
Log.e("FMY", "" + field.getClass());
// 设置属性变量 指向实例
// 若是修饰符不为公共类 这里注意了 当activity
// 控件变量为private的时候 咱们去访问会失败的 要么打破封装系 要么变量改成public
//如 private TextView tv 这种状况 若是不打破封装会直接异常
if (Modifier.PUBLIC != field.getModifiers()) {
// 打破封装性
field.setAccessible(true);
}
// 这里至关于 field= acitivity.obj
field.set(activityOrFragment, obj);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
第三步
在工具类中的inject ()方法调用
/** * 初始化activity和全部注解 * * @param obj 你须要初始化的activity */
public static void inject(Object obj) {
activityClass = obj.getClass();
// 初始化activity布局文件
injectContent(obj);
// 初始化全部控件实例 省去findViewId的痛苦
injectView(obj);
}
这里须要的知识点 如动态代理等 这里你们能够本身百度看下
效果以下
@FmyContentView(R.layout.activity_main)
public class MainActivity extends FragmentActivity {
@FmyViewView(R.id.fl)
private FrameLayout fl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FmyViewInject.inject(this);
}
//当填充的布局中 id为R.id.fl 被点击将调用以下方法
@FmyClickView({R.id.fl})
public void onClick(View v){
Log.e("fmy", "===>>");
}
}
第一步 :
一样写下一个注解
/** * * 设置点击事件的注解 只须要在某方法 上写上此注解便可 如@FmyClickView({R.id.bt1,R.id.bt2}) * @version 1.0 * @author 范明毅 * */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FmyClickView {
/** * 保存全部须要设置点击事件控件的id * @return */
int [] value();
}
第二步:
写下一个代理处理类(我写在工具类中)
/** * 代理处理点击逻辑代码 * * @author 范明毅 * */
static class MInvocationHandler implements InvocationHandler {
//这里咱们到时候回传入activity
private Object target;
// 用户自定义view 的点击事件方法
private Method method;
public MInvocationHandler(Object target, java.lang.reflect.Method method) {
super();
this.target = target;
this.method = method;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 调用用户自定义方法的点击事件 让activity调用中开发者设定的方法
return this.method.invoke(target, args);
}
}
第三步:
在工具类中写下一个方法用于初始化点击事件
/** * 初始化全部控件的点击事件 只须要某方法上写上对应注解和id便可 * * @param activity */
private static void inijectOnClick(Object activityOrFragment) {
//得到全部方法
Method[] methods = null;
methods = activityClass.getMethods();
// 遍历全部的activity下的方法
for (Method method : methods) {
// 获取方法的注解
FmyClickView fmyClickView = method
.getAnnotation(FmyClickView.class);
// 若是存在此注解
if (fmyClickView != null) {
// 全部注解的控件的id
int[] ids = fmyClickView.value();
// 代理处理类
MInvocationHandler handler = new MInvocationHandler(activityOrFragment,
method);
// 代理实例 这里也能够返回 new Class<?>[] { View.OnClickListener.class }中的接口类
//第一个参数用于加载其余类 不必定要使用View.OnClickListener.class.getClassLoader() 你可使用其余的
//第二个参数你所实现的接口
Object newProxyInstance = Proxy.newProxyInstance(
View.OnClickListener.class.getClassLoader(),
new Class<?>[] { View.OnClickListener.class }, handler);
// 遍历全部的控件id 而后设置代理
for (int i : ids) {
try {
Object view = null;
//若是对象是activity
view = activityClass.getMethod("findViewById",
int.class).invoke(activityOrFragment, i);
if (view != null) {
Method method2 = view.getClass().getMethod(
"setOnClickListener",
View.OnClickListener.class);
method2.invoke(view, newProxyInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
第四部:
在工具类的inject()调用便可
/** * 初始化activity和全部注解 * * @param obj * 你须要初始化的activity */
public static void inject(Object obj) {
activityClass = obj.getClass();
// 初始化activity布局文件
injectContent(obj);
// 初始化全部控件实例 省去findViewId的痛苦
injectView(obj);
// 初始化全部控件的点击事件
inijectOnClick(obj);
}