首先解决啥是回调:
我觉得这个例子比较好:某天,我打电话向你请教问题,当然是个难题,你一时想不出解决方法,我又不能拿着电话在那里傻等,于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了。过了XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理。
以Activity和Adapter为例:
我们知道,Activity中的ListVIew的为界面层,其数据要通过放到适配器中去,才能展示。Activity的通过各种逻辑(不论是模拟数据,还是网络加载的数据)得到的数据,一般是通过适配器的构造方法来传递的。这样的话,适配器拿到数据就去一一适配展示了。但是,如果activity想要去拿到adapter的数据呢?比如:我想拿到ListView的每条item的数据(或者某个控件或者item的位置id),想想怎么拿呢?------------------这时候就不好拿了是吧?
下面会上一个Demo,Demo中简单的实现我想通过点击每个item的某个控件那个这个item的位置id。
Adapter的代码:
package com.example.adaptercallbackdemo; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.List; /** * Created by Administrator on 2017/10/19. */ public class MyAdapter extends BaseAdapter { private Context mContext; private ListmList; private LayoutInflater mLayoutInflater; private OnClickMyTextView mOnClickMyTextView;
//构造函数接收MainActivity传递过来的数据 public MyAdapter(Context context, Listlist) { mLayoutInflater = LayoutInflater.from(context); this.mContext = context; this.mList = list; } @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder mViewHolder = null; if (convertView == null) { mViewHolder = new ViewHolder(); convertView = mLayoutInflater.inflate(R.layout.list_item, null); mViewHolder.mTextView = (TextView) convertView.findViewById(R.id.text); convertView.setTag(mViewHolder); } else { mViewHolder = (ViewHolder) convertView.getTag(); } mViewHolder.mTextView.setText(mList.get(position).toString());
//这里便是知道难题答案的地方,setOnClickListener即点击了item的控件TextView,
点击了就能拿到id撒,但是这个时候我要告诉Activity啊,就有了接口回调。
if (mOnClickMyTextView != null){ mViewHolder.mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {
//想到问题的答案的地方。其实这里的mOnClickMyTextView是传来的接口对象,接口就是一种规范,这里的
myTextViewClick
函数
和activity处的
myTextViewClick都要遵守这个规范(即指向同一个接口对象),才能回调成功。
mOnClickMyTextView.myTextViewClick(position);
}
});
}
return convertView;
}
public class ViewHolder {
TextView mTextView;
}
public interface OnClickMyTextView {//创建一个接口类
void myTextViewClick(int id);//创建一个回调函数,实例化接口的时候就要具体化这个回调函数,即要有函数体
}
//注册函数
public void setOnClickMyTextView(OnClickMyTextView onClickMyTextView){
this.mOnClickMyTextView = onClickMyTextView;
}
}
Activity的代码:
public class MainActivity extends AppCompatActivity { private ListView mListView;//声明ListView控件来展示数据 private ArrayListmlist;//列表存放数据 private MyAdapter myAdapter;//自定义的adapter @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.listview); initData(); myAdapter = new MyAdapter(MainActivity.this, mlist);//将数据传到适配器中去 mListView.setAdapter(myAdapter); //接口的调用,获取传递的id
//其实想想,下面的代码在OnCreate方法中调用,必定会执行setOnClickMyTextView方法(里面的
myTextViewClick暂时不会执行),(相当于我Activity在问你Adapter,这个第几个位置啊?即这个的id是多少啊?
这时没点击某一个item,我怎么告诉你id啊,就是上面的遇到难题要思考一下),那就先思考着吧)
//调用了此方法就表示有人联系你了,注册到这来了 myAdapter.setOnClickMyTextView(new MyAdapter.OnClickMyTextView() { @Override public void myTextViewClick(int id) { String string = mlist.get(id); Toast.makeText(MainActivity .this, mlist.get(id), Toast.LENGTH_LONG).show(); } }); } //列表模拟数据 private void initData() { mlist = new ArrayList<>(); for (int i = 0; i < 20; i++) { mlist.add("这是第" + i); } } }
以上各个关键的地方都有代码注释,结合了文章开头的那个例子给出注释。
那个例子说明了“异步+回调”的编程模式。其中,我问完你问题之后,我们在各干各的事,我不用在那一直等你,可以去干别的事,这边是“异步”的过程。
你后来打手机告诉我结果便是一个“回调”过程;我的手机号码必须在以前告诉你,这便是注册回调函数;我的手机号码应该有效并且手机能够接收到你的呼叫,
这是回调函数必须符合接口规范。
对回调的深入思考:
程序的本质就是代码跳转,不管同步异步反射接口虚函数,本质上都是函数调用。函数我们要调用它,就需要它的指针,不同语言通过不同的方式来得到这个指针。而我们定义的接口其实就是一个函数指针,那么那个注册过程,其实就是相当于对那个函数指针赋值。通过这个函数指针来调用我们定义的自实现函数。