【Android - 自定义View】之自定义可下拉刷新或上拉加载的ListView
首先来介绍一下这个自定义View:
- (1)这个自定义View的名称叫做 RefreshableListView ,继承自ListView类;
- (2)在这个自定义View中,用户可以设置是否支持下拉刷新或上拉加载,当然也可以设置为都支持或都不支持;
- (3)在这个自定义View中设置了下拉刷新和上拉加载的回调方法,用户可以自己编写下拉刷新和上拉加载的业务代码。
接下来简单介绍一下这个自定义View中用到的技术点:
- (1)为ListView添加头部和底部布局,分别调用addHeaderView() 和addFooterView() 方法;
- (2)通过ListView的setPadding() 方法设置隐藏ListView的头部和底部布局;
- (3)通过ListView的 post() 方法将头部布局和底部布局的测量操作post到ListView加载完成后进行;
- (4)实现 OnScrollListener 接口,在 onScroll() 和 onScrollStateChanged() 方法中判断当前位置是否可以进行上拉或下拉操作;
- (5)重写 onTouchEvent() 方法,通过手势操作头部布局和底部布局进行下拉刷新和上拉加载操作;
- (6)使用 ObjectAnimator 属性动画进行头部布局中箭头的反转操作;
- (7)定义了回调接口,让用户自己编写下拉刷新和上拉加载的业务代码;
- (8)设置了两个标志位 isRefreshEnabled 和 isLoadEnabled ,让用户自己控制是否可以下拉刷新或上拉加载。
下面是这个自定义View—— RefreshableListView 中的代码:
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView; /**
* 自定义的可下拉刷新或上拉加载的ListView
*/
public class RefreshableListView extends ListView implements AbsListView.OnScrollListener {
private static final int STATE_NORMAL = 0x000; // 正常状态(没有显示头部布局)
private static final int STATE_PULLING = 0x001; // 正在下拉或上拉,但没有达到刷新或加载的要求的状态
private static final int STATE_PREPARED = 0x002; // 达到刷新或加载的要求,松开手指就可以刷新或加载的状态
private static final int STATE_REFRESHING = 0x003; // 正在刷新或加载的状态 private View headerView; // 顶部布局
private ImageView arrow; // 顶部布局中的箭头
private ProgressBar headerProgress; // 顶部布局中的进度条
private TextView headerTip; // 顶部布局中的提示信息
private int headerHeight; // 头部布局的高度
private boolean isRefreshEnabled; // 是否允许下拉刷新
private boolean isRefreshable; // 是否可以下拉刷新 private ProgressBar footerProgress; // 底部布局中的进度条
private TextView footerTip; // 底部布局中的提示信息
private int footerHeight; // 底部布局的高度
private boolean isLoadEnabled; // 是否允许上拉加载
private boolean isLoadable; // 是否可以上拉加载 private int firstItemIndex; // 第一个可见Item的下标
private int visibleItemCount; // 页面中可见的Item的个数
private int totalItemCount; // ListView中加载的Item的总个数 private int firstItemTopPadding; // 第一个Item的top值
private int startY; // 记录手指按下时的Y坐标位置
private int offsetY; // 记录手指拖动过程中Y坐标的偏移量
private int rotateTime; // 旋转次数,用于控制箭头只旋转一次
private boolean isScrollIdle; // 滑动动作是否是停止的 private OnRefreshListener onRefreshListener; public RefreshableListView(Context context) {
super(context);
initView(context);
} public RefreshableListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
} public RefreshableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
} /**
* 初始化界面,添加顶部布局文件到ListView中
*/
private void initView(Context context) {
// 初始化头部布局及布局中的控件
headerView = LayoutInflater.from(context).inflate(R.layout.sideworks_rlv_header, this, false);
arrow = (ImageView) headerView.findViewById(R.id.rlv_header_iv_arrow);
headerProgress = (ProgressBar) headerView.findViewById(R.id.rlv_header_progress_progressbar);
headerTip = (TextView) headerView.findViewById(R.id.rlv_header_tv_tip);
// 初始化底部布局及布局中的控件
View footerView = LayoutInflater.from(context).inflate(R.layout.sideworks_rlv_footer, this, false);
footerProgress = (ProgressBar) footerView.findViewById(R.id.rlv_footer_progress_progressbar);
footerTip = (TextView) footerView.findViewById(R.id.rlv_footer_tv_tip);
// 此时视图刚刚开始初始化,如果直接获取测量值会返回0,因此需要将这个操作post到初始化之后进行
this.post(new Runnable() {
@Override
public void run() {
headerHeight = headerView.getMeasuredHeight();
// 布局文件中,头部布局100dp,底部布局60dp,因此偷个懒,用*0.6的方法得到底部布局的高度
footerHeight = (int) (headerHeight * 0.6);
setViewPadding(-headerHeight, -footerHeight);
}
});
this.addHeaderView(headerView);
this.addFooterView(footerView);
this.setOnScrollListener(this);
} /**
* 设置RefreshableListView的上下边距(用于隐藏头部和底部布局)
*/
private void setViewPadding(int topPadding, int bottomPadding) {
this.setPadding(headerView.getPaddingLeft(), topPadding, headerView.getPaddingRight(), bottomPadding);
} @Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.firstItemIndex = firstVisibleItem;
this.visibleItemCount = visibleItemCount;
this.totalItemCount = totalItemCount;
View firstView = this.getChildAt(firstVisibleItem);
if (firstView != null) {
this.firstItemTopPadding = firstView.getTop();
}
} @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
isScrollIdle = scrollState == OnScrollListener.SCROLL_STATE_IDLE;
} /**
* 监听手指操作的事件(按下、滑动、抬起)
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
// 手指按下时,判断是否可以下拉刷新或上拉加载
case MotionEvent.ACTION_DOWN:
isRefreshable = false;
if (isRefreshEnabled && firstItemIndex == 0 && firstItemTopPadding == -headerHeight) {
isRefreshable = true;
} else if (isLoadEnabled && isScrollIdle && firstItemIndex + visibleItemCount == totalItemCount) {
isLoadable = true;
}
startY = (int) ev.getY();
break;
// 手指移动时,判断是否在下拉刷新或上拉加载,如果是,则动态改变头部布局或底部布局的状态
case MotionEvent.ACTION_MOVE:
offsetY = (int) ev.getY() - startY;
if (isRefreshEnabled && isRefreshable && offsetY > 0) {
setViewPadding(-headerHeight + offsetY, -footerHeight);
if (offsetY >= headerHeight) {
setCurrentState(STATE_PREPARED);
} else {
setCurrentState(STATE_PULLING);
}
} else if (isLoadEnabled && isLoadable && offsetY < 0) {
setViewPadding(-headerHeight, -footerHeight - offsetY);
if (offsetY <= -footerHeight) {
setCurrentState(STATE_PREPARED);
} else {
setCurrentState(STATE_PULLING);
}
}
break;
// 手指抬起时,判断是否下拉或上拉到可以刷新或加载的程度,如果达到程度,则进行刷新或加载
case MotionEvent.ACTION_UP:
if (isRefreshEnabled && isRefreshable && offsetY > 0) {
if (offsetY <= headerHeight) {
setViewPadding(-headerHeight, -footerHeight);
setCurrentState(STATE_NORMAL);
} else {
setViewPadding(0, -footerHeight);
setCurrentState(STATE_REFRESHING);
onRefreshListener.onRefreshing(); // 调用接口的回调方法
}
} else if (isLoadEnabled && isLoadable && offsetY < 0) {
if (offsetY >= -footerHeight) {
setViewPadding(-headerHeight, -footerHeight);
setCurrentState(STATE_NORMAL);
} else {
setViewPadding(-headerHeight, 0);
setCurrentState(STATE_REFRESHING);
onRefreshListener.onLoading(); // 调用接口的回调方法
}
}
isRefreshable = false;
isLoadable = false;
break;
}
return super.onTouchEvent(ev);
} /**
* 根据当前的状态进行相应的处理
*/
private void setCurrentState(int state) {
switch (state) {
// 普通状态:头部布局和尾部布局都隐藏,头部布局中显示箭头不显示进度条,底部布局中不显示进度条
case STATE_NORMAL:
headerProgress.setVisibility(View.GONE);
arrow.setVisibility(View.VISIBLE);
footerProgress.setVisibility(View.GONE);
break;
// 正在下拉后上拉,但没有达到刷新或加载的要求的状态:
// 如果是下拉,则将头部布局中的箭头指向调整为指下,同时改变文本;
// 如果是上拉,则改变文本
case STATE_PULLING:
if (isRefreshEnabled && isRefreshable) {
if (rotateTime == 1) {
ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 180f, 0f);
toUpAnim.setDuration(200);
toUpAnim.start();
rotateTime--;
}
headerTip.setText("下拉可以刷新");
} else if (isLoadEnabled && isLoadable) {
footerTip.setText("上拉加载更多");
}
break;
// 下拉或上拉达到刷新或加载的条件,但还没有松手的状态:
// 如果是下拉,则将头部布局中的箭头指向调整为指上,同时改变文本;
// 如果是上拉,则改变文本
case STATE_PREPARED:
if (isRefreshEnabled && isRefreshable) {
if (rotateTime == 0) {
ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 0f, 180f);
toUpAnim.setDuration(200);
toUpAnim.start();
rotateTime++;
}
headerTip.setText("松开手指刷新");
} else if (isLoadEnabled && isLoadable) {
footerTip.setText("松开手指加载");
}
break;
// 正在刷新或加载的状态:
// 如果是下拉,则隐藏头部布局中的箭头,显示头部布局中的进度条,改变文本;
// 如果是上拉,则显示底部布局中的进度条,改变文本
case STATE_REFRESHING:
if (isRefreshEnabled && isRefreshable) {
arrow.setVisibility(View.GONE);
headerProgress.setVisibility(View.VISIBLE);
if (rotateTime == 1) {
ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 180f, 0f);
toUpAnim.setDuration(200);
toUpAnim.start();
rotateTime--;
}
headerTip.setText("正在刷新......");
} else if (isLoadEnabled && isLoadable) {
footerProgress.setVisibility(View.VISIBLE);
footerTip.setText("正在加载......");
}
break;
}
} /**
* 刷新结束后必须调用这个方法来重置一些参数
*/
public void onRefreshComplete() {
setViewPadding(-headerHeight, -footerHeight);
setCurrentState(STATE_NORMAL);
} /**
* 设置是否允许下拉刷新和上拉加载
*/
public void setEnables(boolean isRefreshEnabled, boolean isLoadEnabled) {
this.isRefreshEnabled = isRefreshEnabled;
this.isLoadEnabled = isLoadEnabled;
} /**
* 监听下拉刷新的接口
*/
interface OnRefreshListener {
void onRefreshing(); // 在下拉刷新的时候回调的方法 void onLoading(); // 在上拉加载的时候回调的方法
} public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
this.onRefreshListener = onRefreshListener;
}
}
头部布局文件 sideworks_rlv_header.xml 中的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100.0dip"
android:background="#DEDEDE"
android:gravity="center"
android:orientation="horizontal"> <RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"> <ImageView
android:id="@+id/rlv_header_iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:contentDescription="@string/app_name"
android:src="@mipmap/img_rlv_arrow" /> <ProgressBar
android:id="@+id/rlv_header_progress_progressbar"
android:layout_width="35.0dip"
android:layout_height="35.0dip"
android:layout_centerInParent="true"
android:visibility="gone" />
</RelativeLayout> <TextView
android:id="@+id/rlv_header_tv_tip"
android:layout_width="wrap_content"
android:layout_height="100.0dip"
android:layout_marginLeft="10.0dip"
android:gravity="center"
android:text="下拉可以刷新"
android:textColor="#444444"
android:textSize="16.0sp"
android:textStyle="bold" /> </LinearLayout>
底部布局文件 sideworks_rlv_footer.xml 中的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"> <LinearLayout
android:id="@+id/rlv_footer_ly_content"
android:layout_width="match_parent"
android:layout_height="60.0dip"
android:background="#DEDEDE"
android:gravity="center"
android:orientation="horizontal"> <ProgressBar
android:id="@+id/rlv_footer_progress_progressbar"
android:layout_width="30.0dip"
android:layout_height="30.0dip"
android:visibility="gone" /> <TextView
android:id="@+id/rlv_footer_tv_tip"
android:layout_width="wrap_content"
android:layout_height="60.0dip"
android:layout_marginLeft="10.0dip"
android:gravity="center"
android:text="上拉加载更多"
android:textColor="#444444"
android:textSize="16.0sp"
android:textStyle="bold" />
</LinearLayout> </LinearLayout>
控件中每一条数据的布局文件 listitem_rlv_listview.xml 中的代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="90.0dip"
android:background="@android:color/white"> <ImageView
android:id="@+id/rlv_listitem_iv_image"
android:layout_width="65.0dip"
android:layout_height="65.0dip"
android:layout_centerVertical="true"
android:layout_marginLeft="10.0dip"
android:contentDescription="@string/app_name"
android:src="@mipmap/img_rlv_listitem_image" /> <TextView
android:id="@+id/rlv_listitem_tv_name"
android:layout_width="wrap_content"
android:layout_height="90.0dip"
android:layout_centerVertical="true"
android:layout_marginLeft="10.0dip"
android:layout_toRightOf="@id/rlv_listitem_iv_image"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="16.0sp" /> <Button
android:id="@+id/rlv_listitem_btn_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10.0dip"
android:text="Download" /> </RelativeLayout>
适配器类文件 ListViewAdapter.java 中的代码:
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast; import java.util.List; /**
* ListView的数据适配器
*/
public class ListViewAdapter extends BaseAdapter {
private Context context;
private List<String> nameList; public ListViewAdapter(Context context, List<String> nameList) {
this.context = context;
this.nameList = nameList;
} @Override
public int getCount() {
return nameList.size();
} @Override
public Object getItem(int position) {
return nameList.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.listitem_rlv_listview, parent, false);
holder = new ViewHolder();
holder.image = (ImageView) convertView.findViewById(R.id.rlv_listitem_iv_image);
holder.name = (TextView) convertView.findViewById(R.id.rlv_listitem_tv_name);
holder.download = (Button) convertView.findViewById(R.id.rlv_listitem_btn_download);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.image.setImageResource(R.mipmap.ic_launcher);
holder.name.setText(nameList.get(position));
holder.download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, nameList.get(position) + " Clicked!", Toast.LENGTH_SHORT).show();
}
});
return convertView;
} private static class ViewHolder {
ImageView image;
TextView name;
Button download;
}
}
主界面的JAVA文件 MainActivity.java 中的代码:
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import java.util.ArrayList;
import java.util.List; public class MainActivity extends AppCompatActivity {
private RefreshableListView listview; // 自定义ListView控件 private List<String> nameList; // RefreshableListView控件中显示的数据的数据集
private ListViewAdapter adapter; // RefreshableListView控件的适配器 // Handler更新UI界面
private Handler refreshHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// 下拉刷新的回调,在列表的最前面插入一条数据
case 0:
nameList.add(0, "新加入的名称!!");
break;
// 上拉加载的回调,在列表的最后添加一条数据
case 1:
nameList.add("新加入的名称!!");
break;
}
// ListView的数据适配器更新数据集
adapter.notifyDataSetChanged();
// 必须调用这个方法,重置头部布局或底部布局的视图
listview.onRefreshComplete();
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} @Override
protected void onResume() {
super.onResume();
// 通过ID找到我们的自定义控件RefreshableListView
listview = (RefreshableListView) this.findViewById(R.id.rlv_lv_listview);
// 初始化RefreshableListView控件中显示的数据集
initNameList();
// 创建RefreshableListView的数据适配器
adapter = new ListViewAdapter(MainActivity.this, nameList);
// 为RefreshableListView适配数据
listview.setAdapter(adapter);
// 设置是否可以下拉刷新或上拉加载,这里设置的是可以下拉刷新,不可以上拉加载
listview.setEnables(true, false);
// 设置RefreshableListView的回调
listview.setOnRefreshListener(new RefreshableListView.OnRefreshListener() {
// 下拉刷新的回调方法,在这个方法中停留2秒后发送一条消息给Handler
@Override
public void onRefreshing() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
refreshHandler.sendEmptyMessage(0);
}
}).start();
} // 上拉加载的回调方法,在这个方法中停留2秒后发送一条消息给Handler
@Override
public void onLoading() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
refreshHandler.sendEmptyMessage(1);
}
}).start();
}
});
} /**
* 初始化RefreshableListView控件中显示的数据集
*/
private void initNameList() {
nameList = new ArrayList<>();
for (int i = 0; i < 15; i++) {
nameList.add("APP名称" + (i + 1));
}
}
}
最后上一张效果图(这张效果图是同时支持下拉刷新和上拉加载功能的),如下:
【Android - 自定义View】之自定义可下拉刷新或上拉加载的ListView的更多相关文章
- Android如何定制一个下拉刷新,上滑加载更多的容器
前言 下拉刷新和上滑加载更多,是一种比较常用的列表数据交互方式. android提供了原生的下拉刷新容器 SwipeRefreshLayout,可惜样式不能定制. 于是打算自己实现一个专用的.但是下拉 ...
- Android之下拉刷新,上啦加载的实现(一)
转载地址http://blog.csdn.net/leehong2005/article/details/12567757#t5 前段时间项目中用到了下拉刷新功能,之前在网上也找到过类似的demo,但 ...
- Android打造(ListView、GridView等)通用的下拉刷新、上拉自动加载的组件
原文 http://blog.csdn.net/bboyfeiyu/article/details/39253051 前言 下 拉刷新组件在开发中使用率是非常高的,基本上联网的APP都会采 ...
- Android XListView下拉刷新、上拉载入更多
source code: https://github.com/Maxwin-z/XListView-Android 提供了两个接口: a) IXListViewListener: 触发下拉刷新.上 ...
- SVPullToRefresh 下拉刷新,上拉加载
https://github.com/Sephiroth87/ODRefreshControl 类似刷新控件,类似qq动画的那种刷新. 一.下载第三方库 https://github.com/samv ...
- DCloud-MUI:下拉刷新、上拉加载
ylbtech-DCloud-MUI:下拉刷新.上拉加载 1. 下拉刷新返回顶部 0. http://dev.dcloud.net.cn/mui/pulldown/ 1. 概述 为实现下拉刷新功能,大 ...
- iOS-SVPullToRefresh下拉刷新,上拉加载(转)
https://github.com/Sephiroth87/ODRefreshControl 类似刷新控件,类似qq动画的那种刷新. 一.下载第三方库 https://github.com/samv ...
- iOS MJRefresh下拉刷新(上拉加载)使用详解
下拉刷新控件目前比较火的有好几种,本人用过MJRefresh 和 SVPullToRefresh,相对而言,前者比后者可定制化.拓展新都更高一点. 因此本文着重讲一下MJRefresh的简单用法. 导 ...
- ListView下拉刷新、上拉载入更多之封装改进
在Android中ListView下拉刷新.上拉载入更多示例一文中,Maxwin兄给出的控件比较强大,前面有详细介绍,但是有个不足就是,里面使用了一些资源文件,包括图片,String,layout,这 ...
随机推荐
- 用Unity做游戏,你需要深入了解一下IL2CPP
这次我们翻译了一篇Unity官方博客上的文章,原文题目为AN INTRODUCTION TO IL2CPP INTERNALS ,作者是从事Unity软件开发的Joshua Peterson.文章的看 ...
- vue-socket.io 及 egg-socket.io 的简单使用
egg-socket.io 的使用 官方文档看这里 egg-socket.io 接下来的内容其实与文档里差不多,介意的童鞋略过就好,目前只是简单的引入,下周往后会写复杂些的逻辑,在后面的文章会介绍. ...
- Spring Boot2 系列教程(二十)Spring Boot 整合JdbcTemplate 多数据源
多数据源配置也算是一个常见的开发需求,Spring 和 SpringBoot 中,对此都有相应的解决方案,不过一般来说,如果有多数据源的需求,我还是建议首选分布式数据库中间件 MyCat 去解决相关问 ...
- CSPS模拟 88
今天我还是个弟弟. 果然唯有AK不可超越.. T1 决策单调性,暴力上整体二分. 极限数据跑的挺快,可是被n<k的脑残测试点qj了.. T2 又是大模拟! T3 想到剩余同种数量的彩球完全等效 ...
- Ubuntu16.04下nvidia驱动+nvidia-docker+cuda9+cudnn7安装
一.宿主机安装nvidia驱动 打开终端,先删除旧的驱动: sudo apt-get purge nvidia* 禁用自带的 nouveau nvidia驱动 sudo gedit /etc/modp ...
- 2019.11.11 洛谷月赛t3
题目背景 由于Y校的老师非常毒瘤,要求\(zhouwc\)在\(csp\)考前最后\(3\)天参加期中考,\(zhouwc\)非常生气,决定消极考试,以涂完卡但全错为目标.现在\(retcarizy\ ...
- csps63总结
这次考试还算可以(吧),暴力都没打满,但是还差很多. T1 强烈推荐我的打法,很好理解并且很好打(虽然稍长) 维护指针指向的值及其是第几个数,然后分类讨论. (诡异构造的序列==随机数据)?? #in ...
- 搞清楚 Python 的迭代器、可迭代对象、生成器
很多伙伴对 Python 的迭代器.可迭代对象.生成器这几个概念有点搞不清楚,我来说说我的理解,希望对需要的朋友有所帮助. 1 迭代器协议 迭代器协议是核心,搞懂了这个,上面的几个概念也就很好理解了. ...
- CDQ分治(学习笔记)
离线算法——CDQ分治 CDQ (SHY)显然是一个人的名字,陈丹琪(MM)(NOI2008金牌女选手). 从归并开始(这里并没有从逆序对开始,是想直接引入分治思想,而不是引入处理对象) 一个很简单的 ...
- 自己实现 aop 和 spring aop
上文说到,我们可以在 BeanPostProcessor 中对 bean 的初始化前化做手脚,当时也说了,我完全可以生成一个代理类丢回去. 代理类肯定要为用户做一些事情,不可能像学设计模式的时候创建个 ...