标签(空格分隔): Android


新版的知乎安卓client有一个有趣的功能,就是在一个item里。向右滑动时整个item会越来越透明,滑动到一半时,整个item就不见了。放开手指就是删除。删除后还能够撤销,第一次看见这个功能觉得非常有意思,用了几天业余时间,我仿造里一个。效果例如以下:

那以下就来想想看怎么实现的,大概能够先分解为三部分:

  • 手指滑动删除item
  • 删除item后的撤销功能
  • 滑动时的效果处理

提醒一下假设你对scroller不熟悉。能够先看一下scroller实现原理

先来看最基本的类CustomSwipeListView源代码:

  1. import android.content.Context;
  2. import android.graphics.Color;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.VelocityTracker;
  7. import android.view.View;
  8. import android.view.ViewConfiguration;
  9. import android.view.WindowManager;
  10. import android.widget.AdapterView;
  11. import android.widget.ListView;
  12. import android.widget.Scroller;
  13. import android.widget.TextView;
  14. /**
  15. * 2015-2-13 自己定义ListView
  16. */
  17. public class CustomSwipeListView extends ListView {
  18. /**
  19. * 当前滑动的ListView position
  20. */
  21. private int slidePosition;
  22. /**
  23. * 手指按下X的坐标
  24. */
  25. private int downY;
  26. /**
  27. * 手指按下Y的坐标
  28. */
  29. private int downX;
  30. /**
  31. * 屏幕宽度
  32. */
  33. private int screenWidth;
  34. /**
  35. * ListView的item
  36. */
  37. private View itemView;
  38. /**
  39. * item里面的内容区域
  40. */
  41. private View contentView;
  42. /**
  43. * 滑动类
  44. */
  45. private Scroller scroller;
  46. /**
  47. * 滑动速度极限值
  48. */
  49. private final int SNAP_VELOCITY = CustomSwipeUtils.convertDptoPx(getContext(), 1000);
  50. /**
  51. * 速度追踪对象
  52. */
  53. private VelocityTracker velocityTracker;
  54. /**
  55. * 是否响应滑动,默觉得不响应
  56. */
  57. private boolean isSlide = false;
  58. /**
  59. * 觉得是用户滑动的最小距离
  60. */
  61. private int mTouchSlop;
  62. /**
  63. * 移除item后的回调接口
  64. */
  65. private RemoveListener mRemoveListener;
  66. /**
  67. * 用来指示item滑出屏幕的方向,向左或者向右,用一个枚举值来标记
  68. */
  69. private RemoveDirection removeDirection;
  70. private boolean isRemoveScroll = false;
  71. /**
  72. * 指定计算哪个点的速度
  73. */
  74. private int mPointerId;
  75. /**
  76. * 获得同意运行一个fling手势动作的最大速度值
  77. */
  78. private int mMaxVelocity;
  79. int velocityX = 0;
  80. // 滑动删除方向的枚举值
  81. public enum RemoveDirection {
  82. RIGHT, LEFT;
  83. }
  84. public CustomSwipeListView(Context context) {
  85. this(context, null);
  86. }
  87. public CustomSwipeListView(Context context, AttributeSet attrs) {
  88. this(context, attrs, 0);
  89. }
  90. public CustomSwipeListView(Context context, AttributeSet attrs, int defStyle) {
  91. super(context, attrs, defStyle);
  92. screenWidth = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
  93. .getDefaultDisplay().getWidth();
  94. scroller = new Scroller(context);
  95. // 检測用户在move前划过的距离,移动距离大于这个距离才開始算滑动
  96. mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  97. mMaxVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();
  98. }
  99. /**
  100. * 设置滑动删除的回调接口
  101. *
  102. * @param removeListener
  103. */
  104. public void setRemoveListener(RemoveListener removeListener) {
  105. this.mRemoveListener = removeListener;
  106. }
  107. /**
  108. * 分发事件。主要做的是推断点击的是那个item, 以及通过postDelayed来设置响应左右滑动事件
  109. */
  110. @Override
  111. public boolean dispatchTouchEvent(MotionEvent event) {
  112. addVelocityTracker(event);
  113. switch (event.getAction()) {
  114. case MotionEvent.ACTION_DOWN:
  115. mPointerId = event.getPointerId(0);
  116. // 假如scroller滚动还没有结束,我们直接返回
  117. if (!scroller.isFinished()) {
  118. return super.dispatchTouchEvent(event);
  119. }
  120. downX = (int) event.getX();
  121. downY = (int) event.getY();
  122. slidePosition = pointToPosition(downX, downY);
  123. // 无效的position, 不做不论什么处理
  124. if (slidePosition == AdapterView.INVALID_POSITION) {
  125. return super.dispatchTouchEvent(event);
  126. }
  127. // 获取我们点击的item view
  128. itemView = getChildAt(slidePosition - getFirstVisiblePosition());
  129. contentView = itemView.findViewById(R.id.ll_cotentview);
  130. break;
  131. case MotionEvent.ACTION_MOVE:
  132. if (Math.abs(getScrollVelocity()) > SNAP_VELOCITY
  133. || (Math.abs(event.getX() - downX) > mTouchSlop && Math.abs(event.getY()
  134. - downY) < mTouchSlop)) {
  135. isSlide = true;
  136. }
  137. break;
  138. case MotionEvent.ACTION_UP:
  139. recycleVelocityTracker();
  140. break;
  141. }
  142. return super.dispatchTouchEvent(event);
  143. }
  144. /**
  145. * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到開始滑动的距离,所以向右边滑动为负值
  146. */
  147. private void scrollRight() {
  148. isRemoveScroll = true;
  149. removeDirection = RemoveDirection.RIGHT;
  150. final int delta = (screenWidth + itemView.getScrollX());
  151. // 调用startScroll方法来设置一些滚动的參数,我们在computeScroll()方法中调用scrollTo来滚动item
  152. scroller.startScroll(itemView.getScrollX(), 0, -delta, 0, Math.abs(delta));
  153. postInvalidate(); // 刷新itemView
  154. }
  155. /**
  156. * 向左滑动。依据上面我们知道向左滑动为正值
  157. */
  158. private void scrollLeft() {
  159. isRemoveScroll = true;
  160. removeDirection = RemoveDirection.LEFT;
  161. final int delta = (screenWidth - itemView.getScrollX());
  162. // 调用startScroll方法来设置一些滚动的參数,我们在computeScroll()方法中调用scrollTo来滚动item
  163. scroller.startScroll(itemView.getScrollX(), 0, delta, 0, Math.abs(delta));
  164. postInvalidate(); // 刷新itemView
  165. }
  166. /**
  167. * 依据手指滚动itemView的距离来推断是滚动到開始位置还是向左或者向右滚动
  168. */
  169. private void scrollByDistanceX() {
  170. // 假设向左滚动的距离大于屏幕的二分之中的一个,就让其删除
  171. if (itemView.getScrollX() >= screenWidth / 2) {
  172. scrollLeft();
  173. } else if (itemView.getScrollX() <= -screenWidth / 2) {
  174. scrollRight();
  175. } else {
  176. scrollToOrigin();
  177. }
  178. }
  179. // 假设滑动速度不快且距离不到1/3,就原地滑动回原点
  180. private void scrollToOrigin() {
  181. isRemoveScroll = false;
  182. int scrollX = itemView.getScrollX();
  183. // 反方向滑动回去
  184. scroller.startScroll(scrollX, 0, -scrollX, 0, 400);
  185. }
  186. /**
  187. * 处理我们拖动ListView item的逻辑
  188. */
  189. @Override
  190. public boolean onTouchEvent(MotionEvent ev) {
  191. if (isSlide && slidePosition != AdapterView.INVALID_POSITION) {
  192. addVelocityTracker(ev);
  193. final int action = ev.getAction();
  194. int x = (int) ev.getX();
  195. switch (action) {
  196. case MotionEvent.ACTION_MOVE:
  197. int deltaX = downX - x;
  198. downX = x;
  199. // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
  200. itemView.scrollBy(deltaX, 0);
  201. setCotentViewAlpha(getAlphaRatio());
  202. velocityX = getScrollVelocity();
  203. return true;
  204. case MotionEvent.ACTION_UP:
  205. Log.i("scrollvelocity x ========== ", velocityX + " " + SNAP_VELOCITY);
  206. if (velocityX > SNAP_VELOCITY) {
  207. scrollRight();
  208. } else if (velocityX < -SNAP_VELOCITY) {
  209. scrollLeft();
  210. } else {
  211. scrollByDistanceX();
  212. }
  213. recycleVelocityTracker();
  214. // 手指离开的时候就不响应左右滚动
  215. isSlide = false;
  216. break;
  217. }
  218. }
  219. // 否则直接交给ListView来处理onTouchEvent事件
  220. return super.onTouchEvent(ev);
  221. }
  222. /**
  223. * 获取移动距离跟透明度的比率。总距离为1/2 屏幕宽,透明度从0~255
  224. */
  225. private int getAlphaRatio() {
  226. int scrollX = Math.abs(itemView.getScrollX());
  227. int xRatio = (int) Math.round(((2 * scrollX) / (float) screenWidth) * 255);
  228. // 透明度最大值为255
  229. xRatio = 255 - (xRatio > 255 ?
  230. 255 : xRatio);
  231. return xRatio;
  232. }
  233. /**
  234. * 设置内容区域的透明度
  235. */
  236. private void setCotentViewAlpha(int xRatio) {
  237. contentView.getBackground().setAlpha(xRatio);
  238. TextView tvTitle = (TextView) contentView.findViewById(R.id.test_title);
  239. TextView tvDate = (TextView) contentView.findViewById(R.id.test_date);
  240. setTextAlpha(xRatio, tvTitle);
  241. setTextAlpha(xRatio, tvDate);
  242. }
  243. /**
  244. * 设置文字的透明色
  245. */
  246. private void setTextAlpha(int ratio, TextView textView) {
  247. int color = textView.getCurrentTextColor();
  248. textView.setTextColor(Color.argb(ratio, Color.red(color), Color.green(color),
  249. Color.blue(color)));
  250. }
  251. @Override
  252. public void computeScroll() {
  253. // 调用startScroll的时候scroller.computeScrollOffset()返回true。
  254. if (scroller.computeScrollOffset()) {
  255. // 让ListView item依据当前的滚动偏移量进行滚动
  256. itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());
  257. setCotentViewAlpha(getAlphaRatio());
  258. postInvalidate();
  259. // 滚动动画结束的时候调用回调接口
  260. if (scroller.isFinished() && isRemoveScroll) {
  261. if (mRemoveListener == null) {
  262. throw new NullPointerException(
  263. "RemoveListener is null, we should called setRemoveListener()");
  264. }
  265. mRemoveListener.removeItem(removeDirection, slidePosition);
  266. // 删除item后要把透明度和坐标恢复到初始值
  267. itemView.scrollTo(0, 0);
  268. setCotentViewAlpha(255);
  269. }
  270. }
  271. }
  272. /**
  273. * 加入用户的速度跟踪器
  274. *
  275. * @param event
  276. */
  277. private void addVelocityTracker(MotionEvent event) {
  278. if (velocityTracker == null) {
  279. velocityTracker = VelocityTracker.obtain();
  280. }
  281. velocityTracker.addMovement(event);
  282. }
  283. /**
  284. * 移除用户速度跟踪器
  285. */
  286. private void recycleVelocityTracker() {
  287. if (velocityTracker != null) {
  288. velocityTracker.clear();
  289. velocityTracker.recycle();
  290. velocityTracker = null;
  291. }
  292. }
  293. /**
  294. * 获取X方向的滑动速度,大于0向右滑动。反之向左
  295. *
  296. * @return
  297. */
  298. private int getScrollVelocity() {
  299. velocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
  300. int velocity = (int) velocityTracker.getXVelocity(mPointerId);
  301. return velocity;
  302. }
  303. /**
  304. * 当ListView item滑出屏幕,回调这个接口 我们须要在回调方法removeItem()中移除该Item,然后刷新ListView
  305. */
  306. public interface RemoveListener {
  307. public void removeItem(RemoveDirection direction, int position);
  308. }
  309. }

代码里面的解释还是非常具体的,我在这就大体说一下上面三点思路:

  1. 滑动删除

    • 手指滑动item抬起手时,计算item的偏移量,假设大于item的1/2宽,就判定为删除

    • 滑动的速度velocityTracker > 1000 dp(dp会转成px) 时,也判定为删除

  2. 滑动的效果

    • 手指滑动的效果是用scroller实现的

    • item的透明度要要依据item的滑动距离来计算,具体的公式为:

      int xRatio = (int) Math.round(((2 * scrollX) / (float) screenWidth) * 255)

    • 须要透明的不止是item的背景,item里的字体让也要透明

    • item事实上分为两个部分,整块item的背景色事实上是灰色,item的内容区域是白色,这样一划动就露出灰色背景

接下来还有自定的adapter。它实现了删除撤销功能

  1. import java.util.List;
  2. import android.content.Context;
  3. import android.os.Handler;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.view.animation.AnimationUtils;
  8. import android.widget.BaseAdapter;
  9. import android.widget.TextView;
  10. import android.widget.Toast;
  11. import com.example.slidecutlistview.CustomSwipeListView.RemoveDirection;
  12. import com.example.slidecutlistview.CustomSwipeListView.RemoveListener;
  13. /**
  14. * 实现撤销动作的Adapter
  15. */
  16. public class CustomSwipeAdapter extends BaseAdapter implements CancelListener, RemoveListener {
  17. private static final int INVALID_POSITION = -1;
  18. protected Context mContext;
  19. private TestModel deleteModel;
  20. // 測试数据的实体类列表
  21. private List<TestModel> testModels;
  22. // 记录删除的item的位置
  23. private int deletedPosition;
  24. // 是否撤销删除的item
  25. private boolean cancelRemoveItem = false;
  26. // 滑动的方向
  27. private RemoveDirection deleteDirection;
  28. // 记录是否上一次弹出框还没消失
  29. private boolean isCountingTime;
  30. // 撤销弹出框的线程
  31. private Runnable dismissRunnable;
  32. private Handler handler;
  33. private CustomSwipeCancelDialog cancelDialog;
  34. public CustomSwipeAdapter(Context context, List<TestModel> Objects) {
  35. mContext = context;
  36. testModels = Objects;
  37. handler = new Handler();
  38. dismissRunnable = new DismissRunnable();
  39. cancelDialog = new CustomSwipeCancelDialog(context);
  40. cancelDialog.setcancelActionListener(this);
  41. }
  42. @Override
  43. public TestModel getItem(int position) {
  44. return testModels.get(position);
  45. }
  46. @Override
  47. public int getCount() {
  48. return testModels.size();
  49. }
  50. @Override
  51. public View getView(int position, View convertView, ViewGroup parent) {
  52. View view;
  53. ViewHolder holder;
  54. if (convertView == null) {
  55. view = LayoutInflater.from(mContext).inflate(R.layout.test_listview_item_view, parent,
  56. false);
  57. holder = new ViewHolder();
  58. holder.tvDate = (TextView) view.findViewById(R.id.test_date);
  59. holder.tvTitle = (TextView) view.findViewById(R.id.test_title);
  60. view.setTag(holder);
  61. } else {
  62. view = convertView;
  63. holder = (ViewHolder) view.getTag();
  64. }
  65. holder.tvTitle.setText(getItem(position).getTestTitle());
  66. holder.tvDate.setText(getItem(position).getTestDate());
  67. if (cancelRemoveItem) {
  68. cancelActionAnimation(view.findViewById(R.id.ll_cotentview), position);
  69. }
  70. return view;
  71. }
  72. class ViewHolder {
  73. TextView tvTitle;
  74. TextView tvDate;
  75. }
  76. /**
  77. * 运行撤销动画
  78. */
  79. private void cancelActionAnimation(View contentView, int undoPosition) {
  80. if (undoPosition == deletedPosition) {
  81. switch (deleteDirection) {
  82. case LEFT:
  83. contentView.startAnimation(AnimationUtils.loadAnimation(mContext,
  84. R.anim.canceldialog_push_left_in));
  85. break;
  86. case RIGHT:
  87. contentView.startAnimation(AnimationUtils.loadAnimation(mContext,
  88. R.anim.canceldialog_push_right_in));
  89. break;
  90. default:
  91. break;
  92. }
  93. clearDeletedObject();
  94. } else {
  95. contentView.clearAnimation();
  96. }
  97. }
  98. /**
  99. * 撤销dialog消失时调用
  100. */
  101. @Override
  102. public void normalAction() {
  103. if (!cancelRemoveItem) {
  104. clearDeletedObject();
  105. }
  106. }
  107. public void clearDeletedObject() {
  108. deleteModel = null;
  109. cancelRemoveItem = false;
  110. deletedPosition = INVALID_POSITION;
  111. }
  112. /**
  113. * 删除后点击撤销的操作
  114. */
  115. @Override
  116. public void executeCancelAction() {
  117. if (deletedPosition <= testModels.size() && deletedPosition != INVALID_POSITION) {
  118. testModels.add(deletedPosition, deleteModel);
  119. cancelRemoveItem = true;
  120. notifyDataSetChanged();
  121. }
  122. }
  123. @Override
  124. public long getItemId(int position) {
  125. return 0;
  126. }
  127. /**
  128. * 滑动删除之后的回调方法
  129. */
  130. @Override
  131. public void removeItem(RemoveDirection direction, int position) {
  132. // 上一个删除item在延迟的时间内,再删除还有一个。要先终止上一个runnable
  133. if (isCountingTime) {
  134. handler.removeCallbacks(dismissRunnable);
  135. }
  136. TestModel model = removeItemByPosition(position, direction);
  137. cancelDialog.setMessage("Delete" + model.getTestTitle()).showCancelDialog();
  138. dismissDialog();
  139. switch (direction) {
  140. case RIGHT:
  141. Toast.makeText(mContext, "向右删除 " + position, Toast.LENGTH_SHORT).show();
  142. break;
  143. case LEFT:
  144. Toast.makeText(mContext, "向左删除 " + position, Toast.LENGTH_SHORT).show();
  145. break;
  146. default:
  147. break;
  148. }
  149. }
  150. /**
  151. * 删除操作并保存被删除对象信息
  152. */
  153. public TestModel removeItemByPosition(int position, RemoveDirection direction) {
  154. if (position < getCount() && position != INVALID_POSITION) {
  155. deleteModel = testModels.remove(position);
  156. deletedPosition = position;
  157. deleteDirection = direction;
  158. notifyDataSetChanged();
  159. return deleteModel;
  160. } else {
  161. throw new IndexOutOfBoundsException("The position is invalid!");
  162. }
  163. }
  164. /**
  165. * 弹出撤销对话框后一段时间内(5秒)还没不论什么操作的话,对话框自己主动消失
  166. */
  167. private void dismissDialog() {
  168. isCountingTime = true;
  169. handler.postDelayed(dismissRunnable, 5000);
  170. }
  171. class DismissRunnable implements Runnable {
  172. @Override
  173. public void run() {
  174. if (cancelDialog.isShowing()) {
  175. cancelDialog.closeCancelDialog();
  176. clearDeletedObject();
  177. isCountingTime = false;
  178. }
  179. }
  180. }
  181. }

这个adapter继承里两个接口,一个是CustomSwipeListView里的RemoveListener和撤销接口CancelListener

撤销操作

  • ListView里检測到删除操作时,回调adapter里的removeItem方法
  • adapter运行删除操作,并保存被删除的item数据,最后展示撤销的dialog
  • 点击撤销,把被删除的数据从新添加在adapter里,并运行撤销动画
  • 假设不做操作五秒后或者点击其它区域,撤销的dialog消失并删除保留的被删除数据

最后附上整个DEMO的github地址

另:此demo部分源代码和思路来自这篇博客这个开源项目

仿知乎安卓client滑动删除撤销ListView的更多相关文章

  1. Android仿微信SlideView聊天列表滑动删除效果

    package com.ryg.slideview; import com.ryg.slideview.MainActivity.MessageItem; //Download by http://w ...

  2. [转]ANDROID仿IOS微信滑动删除_SWIPELISTVIEW左滑删除例子

    转载:http://dwtedx.sinaapp.com/itshare_290.html 本例子实现了滑动删除ListView的Itemdemo的效果.大家都知道.这种创意是来源于IOS的.左滑删除 ...

  3. android中列表的滑动删除仿ios滑动删除

    大家是不是觉得ios列表的滑动删除效果很酷炫?不用羡慕android也可以实现相同的效果 并且可以自定义效果,比如左滑删除,置顶,收藏,分享等等 其实就是自定义listview重写listview方法 ...

  4. 微信小程序开发日记——高仿知乎日报(上)

    本人对知乎日报是情有独钟,看我的博客和github就知道了,写了几个不同技术类型的知乎日报APP 要做微信小程序首先要对html,css,js有一定的基础,还有对微信小程序的API也要非常熟悉 我将该 ...

  5. android QQ消息左滑动删除实例(优化版SwipeListViewEX)

    仿 QQ消息左滑动删除item消息实例 源代码参考:http://blog.csdn.net/gaolei1201/article/details/42677951 自己作了一些调整,全部代码下载地址 ...

  6. ListView + PopupWindow实现滑动删除

    原文:ListView滑动删除 ,仿腾讯QQ(鸿洋_) 文章实现的功能是:在ListView的Item上从右向左滑时,出现删除按钮,点击删除按钮把Item删除. 看过文章后,感觉没有必要把dispat ...

  7. ListView滑动删除效果实现

    通过继承ListView然后结合PopupWindow实现 首先是布局文件: delete_btn.xml:这里只需要一个Button <?xml version="1.0" ...

  8. 下拉刷新列表添加SwipeDismissListViewTouchListener实现滑动删除某一列。

    <Android SwipeToDismiss:左右滑动删除ListView条目Item> Android的SwipeToDismiss是github上一个第三方开源框架(github上的 ...

  9. Android 高级UI设计笔记03:使用ListView实现左右滑动删除Item

    1. 这里就是实现一个很简单的功能,使用ListView实现左右滑动删除Item: (1)当我们在ListView的某个Item,向左滑动显示一个删除按钮,用户点击按钮,即可以删除该项item,并且有 ...

随机推荐

  1. C# Best Practices - Define Proper Classes

    Application Architecture Define the components appropriately for the application and create project ...

  2. MFC下DLL编程(图解)

    DLL(Dynamic Link Library,动态链接库)是微软公司为Windows和OS/2操作系统设计一种供应用程序在运行时调用的共享函数库.DLL是应用程序的一种扩展,也是软件共享和重用的传 ...

  3. BZOJ 1997: [Hnoi2010]Planar( 2sat )

    平面图中E ≤ V*2-6.. 一个圈上2个点的边可以是在外或者内, 经典的2sat问题.. ----------------------------------------------------- ...

  4. [LeetCode]题解(python):006-ZigZag Conversion

    题目来源: https://leetcode.com/problems/zigzag-conversion/ 题意分析: 这道题目是字符串处理的题目.输入一个字符串和一个数字,将字符串填入倒Z形输入字 ...

  5. java ajax初始化

    <script type="text/javascript">    var http_request = false;    function createXMLHt ...

  6. 基于FPGA的key button等开关消抖,按键消抖电路设计

    最近要用上一个key消抖的功能.于是找到了之前写的并放入博客的程序,发现居然全部有问题.http://www.cnblogs.com/sepeng/p/3477215.html —— 有问题,包括很多 ...

  7. activemq在windows下启动报错,闪退问题

    查验了网上各种方法,都没搞定,最后楼主决定按照linux的解决套路来,把windows计算机名称改为纯英文字母,原计算机名:lee_pc,修改后为leepc,然后重启电脑,再重新运行activemq. ...

  8. ddraw 视频下画图 不闪烁的方法

    我们如果是在在RGB视频上画图(直线,矩形等),一般采用双缓冲区继续,使用内存MemoryDC,来实现画的图形在视频上显示不闪烁的功能,但是我们知道用RGB显示视频都是使用GDI进行渲染,这样很耗CP ...

  9. SPOJ1811最长公共子串问题(后缀自动机)

    题目:http://www.spoj.com/problems/LCS/ 题意:给两个串A和B,求这两个串的最长公共子串. 分析:其实本题用后缀数组的DC3已经能很好的解决,这里我们来说说利用后缀自动 ...

  10. 实现一个简单的http请求工具类

    OC自带的http请求用起来不直观,asihttprequest库又太大了,依赖也多,下面实现一个简单的http请求工具类 四个文件源码大致如下,还有优化空间 MYHttpRequest.h(类定义, ...