先讲下原理:

ScrollView的子View 主要分为3部分:head头部,滚动内容,fooder底部

我们实现惯性滑动,以及回弹,都是靠超过head或者fooder 就重新滚动到  ,内容的顶部或者底部。

之前看了Pulltorefresh 他是通过不断改变 head或者 fooder的 pading 值来实现 上拉或者 下拉的效果。感觉有点不流畅,而且层次嵌套得比较多。当然他的好处是扩展性好。

因工作需求,需要层次嵌套少,对性能要求非常高。因此重新自定义了ViewGroup实现。

直接上代码:

  1. package com.example.administrator.customscrollview;
  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.util.AttributeSet;
  5. import android.util.Log;
  6. import android.view.Gravity;
  7. import android.view.MotionEvent;
  8. import android.view.VelocityTracker;
  9. import android.view.View;
  10. import android.view.ViewConfiguration;
  11. import android.view.ViewGroup;
  12. import android.widget.OverScroller;
  13. /**
  14. * 自定义 pulltorefresh Layout
  15. * TODO: ferris 2015年9月11日 18:52:40
  16. */
  17. public class PullTorefreshScrollView extends ViewGroup {
  18. private FoodeLayout fooder_layout;// top and buttom
  19. private View top_layout;
  20. private int desireWidth, desireHeight;
  21. private VelocityTracker velocityTracker;
  22. private int mPointerId;
  23. private float x, y;
  24. private OverScroller mScroller;
  25. private int maxFlingVelocity, minFlingVelocity;
  26. private int mTouchSlop;
  27. protected Boolean isMove = false;
  28. protected float downX = 0, downY = 0;
  29. private int top_hight = 0;
  30. private int scrollYButtom = 0;
  31. private int nScrollYButtom = 0;
  32. private int pullDownMin = 0;
  33. private Boolean isEnablePullDown = true;
  34. private Boolean isFirst=true;
  35. public void setEnablePullDown(Boolean isEnablePullDown) {
  36. this.isEnablePullDown = isEnablePullDown;
  37. }
  38. public PullTorefreshScrollView(Context context) {
  39. super(context);
  40. init(null, 0);
  41. }
  42. public PullTorefreshScrollView(Context context, AttributeSet attrs) {
  43. super(context, attrs);
  44. init(attrs, 0);
  45. }
  46. public PullTorefreshScrollView(Context context, AttributeSet attrs, int defStyle) {
  47. super(context, attrs, defStyle);
  48. init(attrs, defStyle);
  49. }
  50. private void init(AttributeSet attrs, int defStyle) {
  51. // Load attributes
  52. //        final TypedArray a = getContext().obtainStyledAttributes(
  53. //                attrs, R.styleable.PullTorefreshScrollView, defStyle, 0);
  54. //
  55. //
  56. //        a.recycle();
  57. mScroller = new OverScroller(getContext());
  58. maxFlingVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();
  59. minFlingVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
  60. mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  61. }
  62. @Override
  63. protected void onFinishInflate() {
  64. super.onFinishInflate();
  65. fooder_layout = (FoodeLayout) findViewById(R.id.fooder_layout);
  66. top_layout = findViewById(R.id.top_layout);
  67. if (isEnablePullDown) {
  68. fooder_layout.showFooderPull();
  69. } else {
  70. fooder_layout.hideFooder();
  71. }
  72. }
  73. public int getScrollYTop() {
  74. return top_hight;
  75. }
  76. public int getScrollYButtom() {
  77. return scrollYButtom;
  78. }
  79. public int getNScrollYTop() {
  80. return 0;
  81. }
  82. public int getNScrollYButtom() {
  83. return nScrollYButtom;
  84. }
  85. public int measureWidth(int widthMeasureSpec) {
  86. int result = 0;
  87. int measureMode = MeasureSpec.getMode(widthMeasureSpec);
  88. int width = MeasureSpec.getSize(widthMeasureSpec);
  89. switch (measureMode) {
  90. case MeasureSpec.AT_MOST:
  91. case MeasureSpec.EXACTLY:
  92. result = width;
  93. break;
  94. default:
  95. break;
  96. }
  97. return result;
  98. }
  99. public int measureHeight(int heightMeasureSpec) {
  100. int result = 0;
  101. int measureMode = MeasureSpec.getMode(heightMeasureSpec);
  102. int height = MeasureSpec.getSize(heightMeasureSpec);
  103. switch (measureMode) {
  104. case MeasureSpec.AT_MOST:
  105. case MeasureSpec.EXACTLY:
  106. result = height;
  107. break;
  108. default:
  109. break;
  110. }
  111. return result;
  112. }
  113. @Override
  114. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  115. // 计算所有child view 要占用的空间
  116. int width = measureWidth(widthMeasureSpec);
  117. int height = measureHeight(heightMeasureSpec);
  118. desireWidth = 0;
  119. desireHeight = 0;
  120. int count = getChildCount();
  121. for (int i = 0; i < count; ++i) {
  122. View v = getChildAt(i);
  123. if (v.getVisibility() != View.GONE) {
  124. LayoutParams lp = (LayoutParams) v.getLayoutParams();
  125. measureChildWithMargins(v, widthMeasureSpec, 0,
  126. heightMeasureSpec, 0);
  127. //只是在这里增加了垂直或者水平方向的判断
  128. if (v.getId() == R.id.top_layout) {
  129. top_hight = v.getMeasuredHeight();
  130. }
  131. desireWidth = Math.max(desireWidth, v.getMeasuredWidth()
  132. + lp.leftMargin + lp.rightMargin);
  133. desireHeight += v.getMeasuredHeight() + lp.topMargin
  134. + lp.bottomMargin;
  135. }
  136. }
  137. // count with padding
  138. desireWidth += getPaddingLeft() + getPaddingRight();
  139. desireHeight += getPaddingTop() + getPaddingBottom();
  140. // see if the size is big enough
  141. desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
  142. desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
  143. //处理内容比较少的时候,就添加一定的高度
  144. int scrollHight = height + top_hight * 2;
  145. if (scrollHight > desireWidth) {
  146. int offset = scrollHight - desireHeight;
  147. View view = new View(getContext());
  148. view.setBackgroundResource(R.color.top_layout_color);
  149. LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, offset);
  150. addView(view, getChildCount() - 1, lp);
  151. desireWidth = scrollHight;
  152. }
  153. setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec),
  154. resolveSize(desireHeight, heightMeasureSpec));
  155. scrollYButtom = desireHeight - getMeasuredHeight() - top_hight;
  156. nScrollYButtom = desireHeight - getMeasuredHeight();
  157. //如果上啦拖出一半的高度,就代表将要执行上啦
  158. pullDownMin = nScrollYButtom - top_hight / 2;
  159. if(isFirst){
  160. scrollTo(0, top_hight);
  161. isFirst=false;
  162. }
  163. }
  164. @Override
  165. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  166. final int parentLeft = getPaddingLeft();
  167. final int parentRight = r - l - getPaddingRight();
  168. final int parentTop = getPaddingTop();
  169. final int parentBottom = b - t - getPaddingBottom();
  170. if (BuildConfig.DEBUG)
  171. Log.d("onlayout", "parentleft: " + parentLeft + "   parenttop: "
  172. + parentTop + "   parentright: " + parentRight
  173. + "   parentbottom: " + parentBottom);
  174. int left = parentLeft;
  175. int top = parentTop;
  176. int count = getChildCount();
  177. for (int i = 0; i < count; ++i) {
  178. View v = getChildAt(i);
  179. if (v.getVisibility() != View.GONE) {
  180. LayoutParams lp = (LayoutParams) v.getLayoutParams();
  181. final int childWidth = v.getMeasuredWidth();
  182. final int childHeight = v.getMeasuredHeight();
  183. final int gravity = lp.gravity;
  184. final int horizontalGravity = gravity
  185. & Gravity.HORIZONTAL_GRAVITY_MASK;
  186. final int verticalGravity = gravity
  187. & Gravity.VERTICAL_GRAVITY_MASK;
  188. // layout vertical, and only consider horizontal gravity
  189. left = parentLeft;
  190. top += lp.topMargin;
  191. switch (horizontalGravity) {
  192. case Gravity.LEFT:
  193. break;
  194. case Gravity.CENTER_HORIZONTAL:
  195. left = parentLeft
  196. + (parentRight - parentLeft - childWidth) / 2
  197. + lp.leftMargin - lp.rightMargin;
  198. break;
  199. case Gravity.RIGHT:
  200. left = parentRight - childWidth - lp.rightMargin;
  201. break;
  202. }
  203. v.layout(left, top, left + childWidth, top + childHeight);
  204. top += childHeight + lp.bottomMargin;
  205. }
  206. }
  207. }
  208. @Override
  209. protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
  210. return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  211. ViewGroup.LayoutParams.MATCH_PARENT);
  212. }
  213. @Override
  214. public android.view.ViewGroup.LayoutParams generateLayoutParams(
  215. AttributeSet attrs) {
  216. return new LayoutParams(getContext(), attrs);
  217. }
  218. @Override
  219. protected android.view.ViewGroup.LayoutParams generateLayoutParams(
  220. android.view.ViewGroup.LayoutParams p) {
  221. return new LayoutParams(p);
  222. }
  223. public static class LayoutParams extends MarginLayoutParams {
  224. public int gravity = -1;
  225. public LayoutParams(Context c, AttributeSet attrs) {
  226. super(c, attrs);
  227. TypedArray ta = c.obtainStyledAttributes(attrs,
  228. R.styleable.SlideGroup);
  229. gravity = ta.getInt(R.styleable.SlideGroup_layout_gravity, -1);
  230. ta.recycle();
  231. }
  232. public LayoutParams(int width, int height) {
  233. this(width, height, -1);
  234. }
  235. public LayoutParams(int width, int height, int gravity) {
  236. super(width, height);
  237. this.gravity = gravity;
  238. }
  239. public LayoutParams(android.view.ViewGroup.LayoutParams source) {
  240. super(source);
  241. }
  242. public LayoutParams(MarginLayoutParams source) {
  243. super(source);
  244. }
  245. }
  246. /**
  247. * onInterceptTouchEvent()用来询问是否要拦截处理。 onTouchEvent()是用来进行处理。
  248. * <p/>
  249. * 例如:parentLayout----childLayout----childView 事件的分发流程:
  250. * parentLayout::onInterceptTouchEvent()---false?--->
  251. * childLayout::onInterceptTouchEvent()---false?--->
  252. * childView::onTouchEvent()---false?--->
  253. * childLayout::onTouchEvent()---false?---> parentLayout::onTouchEvent()
  254. * <p/>
  255. * <p/>
  256. * <p/>
  257. * 如果onInterceptTouchEvent()返回false,且分发的子View的onTouchEvent()中返回true,
  258. * 那么onInterceptTouchEvent()将收到所有的后续事件。
  259. * <p/>
  260. * 如果onInterceptTouchEvent()返回true,原本的target将收到ACTION_CANCEL,该事件
  261. * 将会发送给我们自己的onTouchEvent()。
  262. */
  263. @Override
  264. public boolean onInterceptTouchEvent(MotionEvent ev) {
  265. final int action = ev.getActionMasked();
  266. if (BuildConfig.DEBUG)
  267. Log.d("onInterceptTouchEvent", "action: " + action);
  268. if (action == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
  269. // 该事件可能不是我们的
  270. return false;
  271. }
  272. boolean isIntercept = false;
  273. switch (action) {
  274. case MotionEvent.ACTION_DOWN:
  275. // 如果动画还未结束,则将此事件交给onTouchEvet()处理,
  276. // 否则,先分发给子View
  277. isIntercept = !mScroller.isFinished();
  278. // 如果此时不拦截ACTION_DOWN时间,应该记录下触摸地址及手指id,当我们决定拦截ACTION_MOVE的event时,
  279. // 将会需要这些初始信息(因为我们的onTouchEvent将可能接收不到ACTION_DOWN事件)
  280. mPointerId = ev.getPointerId(0);
  281. //          if (!isIntercept) {
  282. downX = x = ev.getX();
  283. downY = y = ev.getY();
  284. //          }
  285. break;
  286. case MotionEvent.ACTION_MOVE:
  287. int pointerIndex = ev.findPointerIndex(mPointerId);
  288. if (BuildConfig.DEBUG)
  289. Log.d("onInterceptTouchEvent", "pointerIndex: " + pointerIndex
  290. + ", pointerId: " + mPointerId);
  291. float mx = ev.getX(pointerIndex);
  292. float my = ev.getY(pointerIndex);
  293. if (BuildConfig.DEBUG)
  294. Log.d("onInterceptTouchEvent", "action_move [touchSlop: "
  295. + mTouchSlop + ", deltaX: " + (x - mx) + ", deltaY: "
  296. + (y - my) + "]");
  297. // 根据方向进行拦截,(其实这样,如果我们的方向是水平的,里面有一个ScrollView,那么我们是支持嵌套的)
  298. if (Math.abs(y - my) >= mTouchSlop) {
  299. isIntercept = true;
  300. }
  301. //如果不拦截的话,我们不会更新位置,这样可以通过累积小的移动距离来判断是否达到可以认为是Move的阈值。
  302. //这里当产生拦截的话,会更新位置(这样相当于损失了mTouchSlop的移动距离,如果不更新,可能会有一点点跳的感觉)
  303. if (isIntercept) {
  304. x = mx;
  305. y = my;
  306. }
  307. break;
  308. case MotionEvent.ACTION_CANCEL:
  309. case MotionEvent.ACTION_UP:
  310. // 这是触摸的最后一个事件,无论如何都不会拦截
  311. if (velocityTracker != null) {
  312. velocityTracker.recycle();
  313. velocityTracker = null;
  314. }
  315. break;
  316. case MotionEvent.ACTION_POINTER_UP:
  317. solvePointerUp(ev);
  318. break;
  319. }
  320. return isIntercept;
  321. }
  322. private void solvePointerUp(MotionEvent event) {
  323. // 获取离开屏幕的手指的索引
  324. int pointerIndexLeave = event.getActionIndex();
  325. int pointerIdLeave = event.getPointerId(pointerIndexLeave);
  326. if (mPointerId == pointerIdLeave) {
  327. // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
  328. int reIndex = pointerIndexLeave == 0 ? 1 : 0;
  329. mPointerId = event.getPointerId(reIndex);
  330. // 调整触摸位置,防止出现跳动
  331. x = event.getX(reIndex);
  332. y = event.getY(reIndex);
  333. if (velocityTracker != null)
  334. velocityTracker.clear();
  335. }
  336. }
  337. @Override
  338. public boolean onTouchEvent(MotionEvent event) {
  339. final int action = event.getActionMasked();
  340. if (velocityTracker == null) {
  341. velocityTracker = VelocityTracker.obtain();
  342. }
  343. velocityTracker.addMovement(event);
  344. switch (action) {
  345. case MotionEvent.ACTION_DOWN:
  346. // 获取索引为0的手指id
  347. isMove = false;
  348. mPointerId = event.getPointerId(0);
  349. x = event.getX();
  350. y = event.getY();
  351. if (!mScroller.isFinished())
  352. mScroller.abortAnimation();
  353. break;
  354. case MotionEvent.ACTION_MOVE:
  355. isMove = true;
  356. // 获取当前手指id所对应的索引,虽然在ACTION_DOWN的时候,我们默认选取索引为0
  357. // 的手指,但当有第二个手指触摸,并且先前有效的手指up之后,我们会调整有效手指
  358. // 屏幕上可能有多个手指,我们需要保证使用的是同一个手指的移动轨迹,
  359. // 因此此处不能使用event.getActionIndex()来获得索引
  360. final int pointerIndex = event.findPointerIndex(mPointerId);
  361. float mx = event.getX(pointerIndex);
  362. float my = event.getY(pointerIndex);
  363. moveBy((int) (x - mx), (int) (y - my));
  364. x = mx;
  365. y = my;
  366. break;
  367. case MotionEvent.ACTION_UP:
  368. isMove = false;
  369. velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
  370. float velocityX = velocityTracker.getXVelocity(mPointerId);
  371. float velocityY = velocityTracker.getYVelocity(mPointerId);
  372. completeMove(-velocityX, -velocityY);
  373. if (velocityTracker != null) {
  374. velocityTracker.recycle();
  375. velocityTracker = null;
  376. }
  377. break;
  378. case MotionEvent.ACTION_POINTER_UP:
  379. // 获取离开屏幕的手指的索引
  380. isMove = false;
  381. int pointerIndexLeave = event.getActionIndex();
  382. int pointerIdLeave = event.getPointerId(pointerIndexLeave);
  383. if (mPointerId == pointerIdLeave) {
  384. // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
  385. int reIndex = pointerIndexLeave == 0 ? 1 : 0;
  386. mPointerId = event.getPointerId(reIndex);
  387. // 调整触摸位置,防止出现跳动
  388. x = event.getX(reIndex);
  389. y = event.getY(reIndex);
  390. if (velocityTracker != null)
  391. velocityTracker.clear();
  392. }
  393. break;
  394. case MotionEvent.ACTION_CANCEL:
  395. isMove = false;
  396. break;
  397. }
  398. return true;
  399. }
  400. private Boolean isPull = false;
  401. //此处的moveBy是根据水平或是垂直排放的方向,
  402. //来选择是水平移动还是垂直移动
  403. public void moveBy(int deltaX, int deltaY) {
  404. if (BuildConfig.DEBUG)
  405. Log.d("moveBy", "deltaX: " + deltaX + "    deltaY: " + deltaY);
  406. if (Math.abs(deltaY) >= Math.abs(deltaX)) {
  407. int mScrollY = getScrollY();
  408. if (mScrollY <= 0) {
  409. scrollTo(0, 0);
  410. } else if (mScrollY >= getNScrollYButtom()) {
  411. scrollTo(0, getNScrollYButtom());
  412. } else {
  413. scrollBy(0, deltaY);
  414. }
  415. if (isEnablePullDown && deltaY > 0 && mScrollY >= pullDownMin) {
  416. isPull = true;
  417. Log.d("onlayout", "isPull: true");
  418. }
  419. }
  420. }
  421. private void completeMove(float velocityX, float velocityY) {
  422. int mScrollY = getScrollY();
  423. int maxY = getScrollYButtom();
  424. int minY = getScrollYTop();
  425. if (mScrollY >= maxY) {//如果滚动,超过了 下边界,就回弹到下边界
  426. if (isPull) {//滚动到最底部
  427. mScroller.startScroll(0, mScrollY, 0, getNScrollYButtom() - mScrollY, 300);
  428. invalidate();
  429. //显示雷达
  430. fooder_layout.showFooderRadar();
  431. if (pullDownListem != null) {
  432. pullDownListem.onPullDown();
  433. }
  434. Log.d("onlayout", "isPull: true 滚动到最底部,显示出雷达");
  435. } else {
  436. mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY);
  437. invalidate();
  438. Log.d("onlayout", "isPull: true");
  439. }
  440. } else if (mScrollY <= minY) {//如果滚动,超过了上边界,就回弹到上边界
  441. // 超出了上边界,弹回
  442. mScroller.startScroll(0, mScrollY, 0, minY - mScrollY);
  443. invalidate();
  444. } else if (Math.abs(velocityY) >= minFlingVelocity && maxY > 0) {//大于1页的时候
  445. //            mScroller.fling(0, mScrollY, 0, (int) (velocityY * 1.2f), 0, 0, minY, maxY);
  446. mScroller.fling(0, mScrollY, 0, (int) (velocityY * 2f), 0, 0, getNScrollYTop(), getNScrollYButtom());
  447. invalidate();
  448. }
  449. }
  450. public void ifNeedScrollBack() {
  451. int mScrollY = getScrollY();
  452. int maxY = getScrollYButtom();
  453. int minY = getScrollYTop();
  454. if (mScrollY > maxY) {
  455. // 超出了下边界,弹回
  456. mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY);
  457. invalidate();
  458. } else if (mScrollY < minY) {
  459. // 超出了上边界,弹回
  460. mScroller.startScroll(0, mScrollY, 0, minY - mScrollY);
  461. invalidate();
  462. }
  463. }
  464. @Override
  465. protected void onScrollChanged(int l, int t, int oldl, int oldt) {
  466. super.onScrollChanged(l, t, oldl, oldt);
  467. }
  468. @Override
  469. public void computeScroll() {
  470. if (mScroller.computeScrollOffset()) {
  471. scrollTo(0, mScroller.getCurrY());
  472. postInvalidate();
  473. } else {
  474. Log.d("onlayout", "computeScroll,isMove:"+isMove+",isPull:"+isPull);
  475. if (!isMove && !isPull) {
  476. ifNeedScrollBack();
  477. }
  478. }
  479. }
  480. public void onPullSuccess() {
  481. soomToBack();
  482. }
  483. public void soomToBack() {
  484. int mScrollY = getScrollY();
  485. int maxY = getScrollYButtom();
  486. Log.d("onlayout", "soomToBack: (maxY - mScrollY)="+(maxY - mScrollY)+",maxY="+maxY+",mScrollY="+mScrollY);
  487. // 超出了下边界,弹回
  488. mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY, 300);
  489. invalidate();
  490. postDelayed(new Runnable() {
  491. @Override
  492. public void run() {
  493. fooder_layout.showFooderPull();
  494. isPull = false;
  495. }
  496. }, 310);
  497. }
  498. private PullDownListem pullDownListem;
  499. public void setPullDownListem(PullDownListem pullDownListem) {
  500. this.pullDownListem = pullDownListem;
  501. }
  502. public interface PullDownListem {
  503. public void onPullDown();
  504. }
  505. }

Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多的更多相关文章

  1. Android中自定义ListView实现上拉加载更多和下拉刷新

    ListView是Android中一个功能强大而且很常用的控件,在很多App中都有ListView的下拉刷新数据和上拉加载更多这个功能.这里我就简单记录一下实现过程. 实现这个功能的方法不止一个,Gi ...

  2. YCRefreshView-自定义支持上拉加载更多,下拉刷新。。。

    自定义支持上拉加载更多,下拉刷新,支持自由切换状态[加载中,加载成功,加载失败,没网络等状态]的控件,拓展功能[支持长按拖拽,侧滑删除]可以选择性添加 .具体使用方法,可以直接参考demo. 轻量级侧 ...

  3. Android 开发 上拉加载更多功能实现

    实现思维 开始之前先废话几句,Android系统没有提供上拉加载的控件,只提供了下拉刷新的SwipeRefreshLayout控件.这个控件我们就不废话,无法实现上拉刷新的功能.现在我们说说上拉加载更 ...

  4. SwipeRefreshLayout详解和自定义上拉加载更多

    个人主页 演示Demo下载 本文重点介绍了SwipeRefreshLayout的使用和自定View继承SwipeRefreshLayout添加上拉加载更多的功能. 介绍之前,先来看一下SwipeRef ...

  5. Android项目:使用pulltorefresh开源项目扩展为下拉刷新上拉加载更多的处理方法,监听listview滚动方向

    很多android应用的下拉刷新都是使用的pulltorefresh这个开源项目,但是它的扩展性在下拉刷新同时又上拉加载更多时会有一定的局限性.查了很多地方,发现这个开源项目并不能很好的同时支持下拉刷 ...

  6. 自定义ListView下拉刷新上拉加载更多

    自定义ListView下拉刷新上拉加载更多 自定义RecyclerView下拉刷新上拉加载更多 Listview现在用的很少了,基本都是使用Recycleview,但是不得不说Listview具有划时 ...

  7. android ListView下拉刷新 上拉加载更多

    背景 最近在公司的项目中要使用到ListView的下拉刷新和上拉加载更多(貌似现在是个项目就有这个功能!哈哈),其实这个东西GitHub上很多,但是我感觉那些框架太大,而且我这个项目只用到了ListV ...

  8. android ListView上拉加载更多 下拉刷新功能实现(采用pull-to-refresh)

    Android实现上拉加载更多功能以及下拉刷新功能, 采用了目前比较火的PullToRefresh,他是目前实现比较好的下拉刷新的类库. 目前他支持的控件有:ListView, ExpandableL ...

  9. google官方的下拉刷新+自定义上拉加载更多

    转载请标注转载:http://blog.csdn.net/oqihaogongyuan/article/details/50949118 google官方的下拉刷新+自定义上拉加载更多 现在很多app ...

  10. react-native-page-listview使用方法(自定义FlatList/ListView下拉刷新,上拉加载更多,方便的实现分页)

    react-native-page-listview 对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View.兼容高版本Flat ...

随机推荐

  1. Baxter机器人---安装SDK包(二)

    原创博文,转载请标明出处:--周学伟http://www.cnblogs.com/zxouxuewei/ 一.frist baxter robot workspace root@zxwubuntu-A ...

  2. jQuery中的ajax服务端返回方式详细说明

    http://blog.sina.com.cn/s/blog_6f92e3a70100u3b6.html     上次总结了下ajax的所有参数项,其中有一项dataType是设置具体的服务器返回方式 ...

  3. URAL 1205 By the Underground or by Foot?(SPFA)

    By the Underground or by Foot? Time limit: 1.0 secondMemory limit: 64 MB Imagine yourself in a big c ...

  4. 【原创】Algorithms:原地归并排序

    第一次归并: a[0] a[1] a[2] a[3] a[4] a[5] a[6] 23 8 19 33 15 6 27 ↑             ↑ i     j 最开始i指向a[0],j指向a ...

  5. Spring源码学习之:模拟实现BeanFactory,从而说明IOC容器的大致原理

    spring的IOC容器能够帮我们自动new对象,对象交给spring管之后我们不用自己手动去new对象了.那么它的原理是什么呢?是怎么实现的呢?下面我来简单的模拟一下spring的机制,相信看完之后 ...

  6. Linux comands

    https://www.mankier.com/8/vmstat http://docs.oracle.com/cd/E23824_01/html/821-1451/spmonitor-22.html ...

  7. php开发memcached

    一.memcached 简介 memcached是高性能的分布式内存缓存服务器.一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度.提高可扩展 性.它可以应对任意 ...

  8. 开放平台-web实现人人网第三方登录

    应用场景     web应用通过人人网登录授权实现第三方登录.   操作步骤     1  注册成为人人网开放平台开发者         http://app.renren.com/developer ...

  9. shell之函数

    function 所有函数在使用前必须定义.这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用.调用函数仅使用其函数名即可.可以将函数看作是脚本中的一段代码,但是有一个主 ...

  10. 一、PHP MongoDB Windows7_64位安装与配置

    NoSQL现在非常的流行了,由于我所在的公司环境问题,目前还用不到这种数据库,出于好奇,翻了翻资料,也算自学了一下.在此做下记录. 我的本机环境:APMServ5.2.6,PHP肯定就是5.2了. 1 ...