先讲下原理:

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. 了解CentOS及周边

    CentOS相关介绍 CentOS是Community ENTerprise Operating System的简称RHEL的全称则是Red Hat Enterprise LinuxFedoro Co ...

  2. meta是什么意思?

    META标签,是HTML语言head区的一个辅助性标签.在几乎所有的page里,我们都可以看 到类似下面这段html代码: -------------------------------------- ...

  3. 【BZOJ1011】【HNOI2008】遥远的行星

    奇奇怪怪突然出戏的奇葩题 原题: 直线上N颗行星,X=i处有行星i,行星J受到行星I的作用力,当且仅当i<=AJ.此时J受到作用力的大小为 Fi->j=Mi*Mj/(j-i) 其中A为很小 ...

  4. 【NOIP2014】飞扬的小鸟

    看syq的代码写出来的,chty_orz 原题: Flappy Bird 是一款风靡一时的休闲手机游戏.玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙.如果小 ...

  5. ExtJS控件样式修改及美化

    Extjs项目对富客户端开发提供了强有力的支持,甚至改变了前端的开发方式,使得开发变得更加趋向于“面向组件”.对界面的美化而言,也是根本性的改变.普通的网页美工面对extjs项目根本无法下手,需要脚本 ...

  6. 论文笔记之:Semi-Supervised Learning with Generative Adversarial Networks

    Semi-Supervised Learning with Generative Adversarial Networks 引言:本文将产生式对抗网络(GAN)拓展到半监督学习,通过强制判别器来输出类 ...

  7. Awesome Deep Vision

    Awesome Deep Vision  A curated list of deep learning resources for computer vision, inspired by awes ...

  8. javascript实现prim算法

    <script type="text/javascript"> //图的构建 function vnode() { this.visited = 0; this.ver ...

  9. C++仿函数和typename的用法

    1.仿函数的定义是很简单的,就是一个重载了括号()运算符的类,也被称为函数对象. 主要是用于个性化扩展算法对象.stl中实现了好多算法,每个算法都可以完成日常的大部分工作,设计者还允许你在这些强大的算 ...

  10. IntelliJ IDEA currently

    https://www.jetbrains.com/help/idea/2016.2/creating-a-project-from-scratch.html https://www.jetbrain ...