之前一直写的是ListVIew下拉刷新,可是好多朋友都说要RecycleView的下拉刷新和滑动载入。事实上,这个原理都是几乎相同。抽出时间,我就写了下RecycleView的下拉刷新和滑动载入很多其它。因此,这才写到博客里,记录一下。

在大家阅读这篇博客前。大家须要了解的知识

1.Scroller。实现弹性滑动的类,这个是经经常使用到的,不懂的请自觉先学习Scroller的知识。

2.事件分发机制。

事件是以ACTION_DOWN開始到ACTION_UP货ACTION_CANCEL结束的一个序列,期间事件分发,能够通过onInterceptTouchEvent方法和dispatchTouchEvent进行事件的阻止和消费。

3.RecyclerView的基本使用。

比方怎样加入一个Decoration.

4.onSizeChange的触发时机。onSizeChange()在View的layout中触发,它运行在全部控件的onMeasure()之后,因此能够直接获取到控件的測量长和宽。
       总体的思路:採用的是LinearLayout+RecyclerView的组合。在LinearLayout中加入HeaderView和FooterView。

当RecyclerView滑动到了最顶部,则能够触发下拉事件;当RecyclerView滑动到了底部,则能够触发滑动载入很多其它的事件。然后在通过事件分发。进行滑动事件的处理。

       先看一下效果:
   以下是自己定义View的代码,后面会逐一分析代码块:
package com.mjc.recyclerviewdemo.refresh;

import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.Scroller; import com.mjc.recyclerviewdemo.CustomItemDecoration; /**
* Created by mjc on 2016/3/11.
*/
public class PullToRefreshRecycleView extends LinearLayout { //头部
private IHeaderView mHeaderView;
private int mHeaderHeight; //尾部
private CustomFooterView mFooterView;
private int mFooterHeight; //阻尼系数,越大,阻力越大
public static final float RATIO = 0.5f;
//当前是否阻止事件
private boolean isIntercept;
//是否正在刷新
private boolean isRefreshing;
//滑动类
private Scroller mScroller;
//刷新的View
private RecyclerView mRefreshView; private Context mContext; private int mMaxScrollHeight; private boolean isFirst = true; public static final int NORMAL = 0;
public static final int PULL_TO_REFRESH = 1;
public static final int RELEASE_TO_REFRESH = 2;
public static final int REFRESING = 3;
private int mCurrentState;
private int mTouchSlop; private OnRefreshListener listener; private boolean isPullDownMotion;
private int lastVisible; public PullToRefreshRecycleView(Context context) {
super(context);
init(context);
} public PullToRefreshRecycleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
} private void init(Context context) {
mContext = context;
this.setOrientation(VERTICAL);
mRefreshView = getRefreshView();
mRefreshView.setBackgroundColor(Color.WHITE);
LayoutParams listParams = new LayoutParams(-1, -1);
mRefreshView.setLayoutParams(listParams);
addView(mRefreshView);
//加入HeaderView
mHeaderView = new CustomHeaderView(context);
LayoutParams params = new LayoutParams(-1, -2);
mHeaderView.setLayoutParams(params);
addView(mHeaderView, 0);
//加入FooterView
mFooterView = new CustomFooterView(context);
LayoutParams fParams = new LayoutParams(-1, 200);
mFooterView.setLayoutParams(fParams);
addView(mFooterView, -1);
//弹性滑动实现
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//第一次获取相关參数,并隐藏HeaderView,FooterView
if (isFirst) {
mHeaderHeight = mHeaderView.getMeasuredHeight();
mMaxScrollHeight = mHeaderHeight * 3;
resetHeaderLayout(-mHeaderHeight); mFooterHeight = mFooterView.getMeasuredHeight();
resetFooterLayout(-mFooterHeight);
Log.v("@mHeaderHeight", mHeaderHeight + "");
Log.v("@mFooterHeight", mFooterHeight + "");
isFirst = false;
}
} private void resetHeaderLayout(int offset) {
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
params.topMargin = offset;
mHeaderView.requestLayout();
} private void resetFooterLayout(int offset) {
LayoutParams params = (LayoutParams) mFooterView.getLayoutParams();
params.bottomMargin = offset;
mFooterView.requestLayout();
} //按下时的位置,当事件被阻止时,第一次ActionDown事件,onTouchEvent无法获取这个位置
//须要在onInterceptTouchEvent获取
private float downY; @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//假设当前是正在刷新而且是下拉状态,则当前视图处理事件
if (isRefreshing && mScrollY < 0) {
return true;
}
//假设当前是刷新状态,而且处于上拉状态,则视图不可进入下拉状态
if (mScrollY >= 0 && isRefreshing)
return false;
boolean isIntercept = false;
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE: //假设达到了滑动条件
if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
if (ev.getY() - downY > 0) {//下拉
isIntercept = isEnablePullDown();
if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
isPullDownMotion = true; } else {//上滑
isIntercept = isEnableLoadMore();
if (isIntercept)//false表示上滑状态
isPullDownMotion = false;
}
} else {
isIntercept = false;
} break;
case MotionEvent.ACTION_CANCEL:
//假设返回true,子视图假设包括点击事件。则无法进行处理
isIntercept = false;
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
break;
}
return isIntercept;
} //记录当前滑动的位置
private int mScrollY; @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//第一次推断时,downY仅仅能从intercept中获取,之后从这里获取
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float dY = event.getY() - downY;
if (isPullDownMotion)//下拉
doPullDownMoveEvent(dY);
else {//自己主动载入很多其它
doLoadMoreEvent(dY);
}
break;
case MotionEvent.ACTION_UP: if (isPullDownMotion) {
//处理下拉结果
doPullDownResult();
} else {
//处理滑动载入很多其它结果
doLoadMoreResult();
} break;
case MotionEvent.ACTION_CANCEL:
//同ACTION_UP
if (isPullDownMotion) {
doPullDownResult();
} else {
doLoadMoreResult(); } break;
}
return true;
} /**
* 处理载入很多其它
*/
private void doLoadMoreResult() {
//手指松开时,假设FooterView,没有全然滑动出来。自己主动滑动出来
scrollTo(0, mFooterHeight);
mScrollY = getScrollY();
if (!isRefreshing) {
isRefreshing = true;
if (listener != null)
listener.onLoadMore();
} } /**
* 载入很多其它完毕后调用
*/
public void completeLoadMore() {
scrollTo(0, 0);
mScrollY = 0;
isRefreshing = false;
LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
int count = manager.getItemCount();
if (count > lastVisible + 1)//载入了很多其它数据
mRefreshView.scrollToPosition(lastVisible + 1);
} //处理载入很多其它
private void doLoadMoreEvent(float y) {
int scrollY = (int) (mScrollY - y);
if (scrollY < 0) {
scrollY = 0;
} if (scrollY > mFooterHeight) {
scrollY = mFooterHeight;
}
Log.v("@scrollY", scrollY + "");
scrollTo(0, scrollY);
} /**
* 处理释放后的操作
*/
private void doPullDownResult() {
//先获取如今滑动到的位置
mScrollY = getScrollY();
switch (mCurrentState) {
case PULL_TO_REFRESH:
mCurrentState = NORMAL;
mHeaderView.onNormal();
smoothScrollTo(0);
break;
case RELEASE_TO_REFRESH:
//松开时,假设是释放刷新。则開始进行刷新动作
if (!isRefreshing) {
//滑动到指定位置
smoothScrollTo(-mHeaderHeight); mHeaderView.onRefreshing();
isRefreshing = true;
if (listener != null) {
//运行刷新回调
listener.onPullDownRefresh(); }
//假设当前滑动位置太靠下,则滑动到指定刷新位置
} else if (mScrollY < -mHeaderHeight) {
smoothScrollTo(-mHeaderHeight);
}
break; }
} /**
* 获取到数据后,调用
*/
public void completeRefresh() {
isRefreshing = false;
mCurrentState = NORMAL;
smoothScrollTo(0);
} private void doPullDownMoveEvent(float y) {
int scrollY = (int) (mScrollY - y * RATIO);
if (scrollY > 0) {
scrollY = 0;
}
if (scrollY < -mMaxScrollHeight) {
scrollY = -mMaxScrollHeight;
}
scrollTo(0, scrollY);
if (isRefreshing)
return;
//设置对应的状态
if (scrollY == 0) {
mCurrentState = NORMAL;
mHeaderView.onNormal();
} else if (scrollY <= 0 && scrollY > -mHeaderHeight) {
mCurrentState = PULL_TO_REFRESH;
mHeaderView.onPullToRefresh(Math.abs(scrollY));
} else if (scrollY <= -mHeaderHeight && scrollY >= -mMaxScrollHeight) {
mCurrentState = RELEASE_TO_REFRESH;
mHeaderView.onReleaseToRefresh(Math.abs(scrollY));
}
} /**
* 从当前位置滑动到指定位置
* @param y 滑动到的位置
*/
private void smoothScrollTo(int y) {
int dY = y - mScrollY;
mScroller.startScroll(0, mScrollY, 0, dY, 500);
invalidate(); } private RecyclerView getRefreshView() {
mRefreshView = new RecyclerView(mContext);
mRefreshView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));
mRefreshView.addItemDecoration(new CustomItemDecoration(mContext, CustomItemDecoration.VERTICAL_LIST));
return mRefreshView;
} public void setAdapter(RecyclerView.Adapter adapter) {
mRefreshView.setAdapter(adapter);
} /**
* 推断列表是否在最顶端
* @return
*/
private boolean isEnablePullDown() {
LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
int firstVisible = manager.findFirstVisibleItemPosition();
//当前还没有数据,能够进行下拉
if(manager.getItemCount()==0)
return true;
return firstVisible == 0 && manager.getChildAt(0).getTop() == 0;
} /**
* 推断列表是否滑动到了最底部
* @return
*/
private boolean isEnableLoadMore() {
LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
lastVisible = manager.findLastVisibleItemPosition();
int totalCount = manager.getItemCount();
//假设没有数据,仅仅能下拉刷新
if (totalCount == 0)
return false;
int bottom = manager.findViewByPosition(lastVisible).getBottom();
int decorHeight = manager.getBottomDecorationHeight(mRefreshView.getChildAt(0));
//最后一个child的底部位置在当前视图的上面
return totalCount == lastVisible + 1 && bottom + decorHeight <= getMeasuredHeight();
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
mScrollY = mScroller.getCurrY();
invalidate();
}
} /**
* 设置Footer的内容
*/
public void setFooterViewState(boolean hasMoreData){
if(hasMoreData){
mFooterView.onRefreshing();
}else{
mFooterView.onNoData();
}
}
public interface OnRefreshListener {
void onPullDownRefresh(); void onLoadMore();
} public void setOnRefreshListener(OnRefreshListener listener) { this.listener = listener;
}
}
    接下来一步一步的进行分析。
    首先,我们在构造方法中。调用了init(Context)方法,例如以下:
  private void init(Context context) {
mContext = context;
this.setOrientation(VERTICAL);
mRefreshView = getRefreshView();
mRefreshView.setBackgroundColor(Color.WHITE);
LayoutParams listParams = new LayoutParams(-1, -1);
mRefreshView.setLayoutParams(listParams);
addView(mRefreshView);
//加入HeaderView
mHeaderView = new CustomHeaderView(context);
LayoutParams params = new LayoutParams(-1, -2);
mHeaderView.setLayoutParams(params);
addView(mHeaderView, 0);
//加入FooterView
mFooterView = new CustomFooterView(context);
LayoutParams fParams = new LayoutParams(-1, 200);
mFooterView.setLayoutParams(fParams);
addView(mFooterView, -1);
//弹性滑动实现
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
方法中。我们构造了HeaderView。RecyclerView以及FooterView。HeaderView和FooterView是简单的自己定义View,RecyclerView是直接构造的。而且在init()方法中。构造了Scroller,用于后面的弹性滑动须要。
接着,后面会运行onSizeChange方法:
    @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//第一次获取相关參数,并隐藏HeaderView。FooterView
if (isFirst) {
mHeaderHeight = mHeaderView.getMeasuredHeight();
mMaxScrollHeight = mHeaderHeight * 3;
resetHeaderLayout(-mHeaderHeight); mFooterHeight = mFooterView.getMeasuredHeight();
resetFooterLayout(-mFooterHeight);
Log.v("@mHeaderHeight", mHeaderHeight + "");
Log.v("@mFooterHeight", mFooterHeight + "");
isFirst = false;
}
}

设置了一个isFirst变量。防止反复设置里面的代码。

在这种方法里面,我们获取了HeaderView,FooterView的測量高。而且,我们设置了HeaderView。FooterView的margin值,隐藏了头部和尾部。

    再接着,就是与用户的交互过程,即用户的触摸事件。这个实现过程。分成两块,一块是下拉刷新,一块是滑动究竟部自己主动载入。这里我们一起分析。

 //按下时的位置,当事件被阻止时。第一次ActionDown事件,onTouchEvent无法获取这个位置
//须要在onInterceptTouchEvent获取
private float downY; @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//假设当前是正在刷新而且是下拉状态,则当前视图处理事件
if (isRefreshing && mScrollY < 0) {
return true;
}
//假设当前是刷新状态。而且处于上拉状态。则视图不可进入下拉状态
if (mScrollY >= 0 && isRefreshing)
return false;
boolean isIntercept = false;
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE: //假设达到了滑动条件
if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
if (ev.getY() - downY > 0) {//下拉
isIntercept = isEnablePullDown();
if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
isPullDownMotion = true; } else {//上滑
isIntercept = isEnableLoadMore();
if (isIntercept)//false表示上滑状态
isPullDownMotion = false;
}
} else {
isIntercept = false;
} break;
case MotionEvent.ACTION_CANCEL:
//假设返回true,子视图假设包括点击事件,则无法进行处理
isIntercept = false;
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
break;
}
return isIntercept;
}
onInterceptTouchEvent的作用,假设返回值为true,表示拦截事件。则事件交个当前控件进行处理,子View无法接收到事件;否则事件交给子View处理。  我们要知道,一般,一个事件序列,仅仅能由一个控件处理,也就是说。假设这个控件消费了ACTION_DOWN事件,那么,后面的ACTION_MOVE等都会交给他处理。可是。假设他的parentView在ACTION_MOVE中,拦截了事件,事件将会转交给ParentView的onTouchEvent处理。

 然后,開始分析代码,
  //假设当前是正在刷新而且是下拉状态,则当前视图处理事件
if (isRefreshing && mScrollY < 0) {
return true;
}
//假设当前是刷新状态,而且处于上拉状态。则视图不可进入下拉状态
if (mScrollY >= 0 && isRefreshing)
return false;

假设当前为下拉而且在刷新状态,则返回true,表示拦截事件,RecyclerView不可滑动。假设当前是滑动载入很多其它。而且刷新状态。则不拦截,由于后面我想在滑动载入很多其它时,RecyclerView能够滑动。  截止后面。在ACTION_DOWN事件中,我们记录下按下的y轴位置。然后是ACTION_MOVE;

     //假设达到了滑动条件
if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
if (ev.getY() - downY > 0) {//下拉
isIntercept = isEnablePullDown();
if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
isPullDownMotion = true; } else {//上滑
isIntercept = isEnableLoadMore();
if (isIntercept)//false表示上滑状态
isPullDownMotion = false;
}
} else {
isIntercept = false;
}
mTouchSlop是滑动的最小值。假设小于这个值,我们觉得没有滑动。大于这个值才算滑动。

假设当前滑动,大于这个值,继续走里面的if推断,假设当前是下拉状态,而且是能够下拉。那么拦截事件,否则进行滑动载入很多其它。假设满足滑动载入很多其它的条件,那么能够向上滑动。而且整个过程,用isPullDownMotion记录下了是向上还是向下的动作。后面在onTouchEvent中须要使用。最后,ACTION_UP和ACTION_CANCEL不拦截。假设拦截,会影响到子View的点击事件。

    最后是onTouchEvent
  //记录当前滑动的位置
private int mScrollY; @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//第一次推断时,downY仅仅能从intercept中获取。之后从这里获取
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float dY = event.getY() - downY;
if (isPullDownMotion)//下拉
doPullDownMoveEvent(dY);
else {//自己主动载入很多其它
doLoadMoreEvent(dY);
}
break;
case MotionEvent.ACTION_UP: if (isPullDownMotion) {
//处理下拉结果
doPullDownResult();
} else {
//处理滑动载入很多其它结果
doLoadMoreResult();
} break;
case MotionEvent.ACTION_CANCEL:
//同ACTION_UP
if (isPullDownMotion) {
doPullDownResult();
} else {
doLoadMoreResult(); } break;
}
return true;
}
看下拉环节(滑动载入很多其它相似,不再介绍),下拉过程ACTION_MOVE中先调用doPullDownMoveEvent,然后在ACTION_UP中调用了doPullDownResult。先看duPullDownMoveEvent
 private void doPullDownMoveEvent(float y) {
int scrollY = (int) (mScrollY - y * RATIO);
if (scrollY > 0) {
scrollY = 0;
}
if (scrollY < -mMaxScrollHeight) {
scrollY = -mMaxScrollHeight;
}
scrollTo(0, scrollY);
if (isRefreshing)
return;
//设置对应的状态
if (scrollY == 0) {
mCurrentState = NORMAL;
mHeaderView.onNormal();
} else if (scrollY <= 0 && scrollY > -mHeaderHeight) {
mCurrentState = PULL_TO_REFRESH;
mHeaderView.onPullToRefresh(Math.abs(scrollY));
} else if (scrollY <= -mHeaderHeight && scrollY >= -mMaxScrollHeight) {
mCurrentState = RELEASE_TO_REFRESH;
mHeaderView.onReleaseToRefresh(Math.abs(scrollY));
}
}
先计算滑动的位置,把滑动的位置限制在-mMaxScrollHeight和0之间,这样就不会滑动到其它地方。然后调用View的scrollTo方法,滑动到对应位置。

这样就完毕了触摸滑动。    后面。我们在通过滑动的位置,设置对应的状态。并回调HeaderView的各个状态的方法。

然后再看doPullDownResult
/**
* 处理释放后的操作
*/
private void doPullDownResult() {
//先获取如今滑动到的位置
mScrollY = getScrollY();
switch (mCurrentState) {
case PULL_TO_REFRESH:
mCurrentState = NORMAL;
mHeaderView.onNormal();
smoothScrollTo(0);
break;
case RELEASE_TO_REFRESH:
//松开时。假设是释放刷新,则開始进行刷新动作
if (!isRefreshing) {
//滑动到指定位置
smoothScrollTo(-mHeaderHeight); mHeaderView.onRefreshing();
isRefreshing = true;
if (listener != null) {
//运行刷新回调
listener.onPullDownRefresh(); }
//假设当前滑动位置太靠下,则滑动到指定刷新位置
} else if (mScrollY < -mHeaderHeight) {
smoothScrollTo(-mHeaderHeight);
}
break; }
}
这种方法,就是手指松开屏幕时触发。然后推断移动过程中的状态。假设是下拉刷新状态,则又一次恢复到下拉之前,调用smoothScrollTo(后面分析详细实现)。弹性滑动到初始位置。并设置状态为NORMAL状态。    假设松开时,是释放刷新状态。那么。先弹性滑动到刷新位置,并运行回调方法。
    如今分析。弹性滑动 smoothScrollTo
   /**
* 从当前位置滑动到指定位置
* @param y 滑动到的位置
*/
private void smoothScrollTo(int y) {
int dY = y - mScrollY;
mScroller.startScroll(0, mScrollY, 0, dY, 500);
invalidate(); }

这种方法,必须要配合computeScroll使用。不然是没有效果的。详细的原因,须要查看View的绘制流程,这里我就不详细分析。

    @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
mScrollY = mScroller.getCurrY();
invalidate();
}
}
这个过程,是从Scroller的startScroll方法開始的,这种方法,调用后。Scroller的computeScrollOffset仅仅要动作没有运行完,就会一直返回true。调用了startScroll方法。须要调用invalide()来引起computeScroll方法的调用,而里面scrollTo方法。才是真正实现位移的原因。里面再调用invalidate又又一次引起了computeScroll方法,直到Scroller的computeOffset方法返回false。

   这样,每次都移动一小段位置,就实现了平滑滑动的效果。

用法。布局文件
<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"
tools:context=".MainActivity"> <com.mjc.recyclerviewdemo.refresh.PullToRefreshRecycleView
android:id="@+id/prrv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Activity中
  mPRRV = (PullToRefreshRecycleView) findViewById(R.id.prrv);
mPRRV.setOnRefreshListener(new PullToRefreshRecycleView.OnRefreshListener() {
@Override
public void onPullDownRefresh() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
datas.add(0, "add");
mAdapter.notifyDataSetChanged();
mPRRV.completeRefresh();
}
}, 2000); } @Override
public void onLoadMore() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
datas.add("李四");
datas.add("王五");
datas.add("张三");
datas.add("李四");
datas.add("王五");
datas.add("张三"); mAdapter.notifyDataSetChanged();
mPRRV.completeLoadMore();
}
}, 1000); }
});
附:源代码

最新文章

  1. 第01章(认识Java)
  2. DEV控件中GridView中的复选框与CheckBox实现联动的全选功能
  3. JavaSpring
  4. ofo走出校园观察:市场定位导致产品错位?
  5. 【DIOCP知识库】连接上下文TIocpClientContext
  6. (译)开发优秀的虚拟现实体验:从开发I Expect You to Die中总结的六个要点
  7. free 命令解释
  8. S3C2440的LCD虚拟显示测试
  9. SolrCloud阶段总结
  10. python 接口自动化测试--框架整改(五)
  11. web组件工具之获取表单数据:webUtils
  12. Type &#39;&#39; cannot conform to protocol &#39;&#39; because it has requirements that cannot be satisfied
  13. 转载-CentOS7关闭防火墙
  14. java中二维数组内存分配
  15. 洛谷P3085 [USACO13OPEN]阴和阳Yin and Yang(点分治,树上差分)
  16. Centos 7 安装 Mysql 5.5 5.6 5.7
  17. vue数组操作不更新视图问题
  18. 并发编程之 Semaphore 源码分析
  19. Office 365实现单点登录系列(3)—使用Azure AD Connect 进行目录同步
  20. jQuery技巧笔记

热门文章

  1. BZOJ4472
  2. JavaScript 回车键绑定登录 事件 常用键位码(keyCode)
  3. Java基础学习总结(11)——重载与重写
  4. ArcGIS api for javascript——地图配置-
  5. Objective-C学习笔记(十)——循环语句for和do-while的使用
  6. 学习中 常用到的string内置对象方法的总结
  7. Aizu - 2564 Tree Reconstruction 并查集
  8. jQuery - 设置内容和属性 设置内容 - text()、html() 以及 val() , 设置属性 - attr()
  9. UVA Building designing
  10. SQL SERVER 2014无法启动T-SQL调试的解决方法(亲自实践)