最近在一直在用今日头条,发现在我的频道编辑时的拖拽排序体验非常有意思,这种拖拽功能其实在支付宝等app上也频繁使用,于是打算自己研究一下,网上虽然有很多类似于此类功能的博客,但实现的都不是特别完美,效果总有瑕疵,今天我分享一个完美体验版,大家用了就知道!

老规矩,先上效果:

准备工作

要想实现这个效果,首先你要了解这几个方面的知识,有欠缺的同学赶紧先补一下:
* 使用WindowManager添加悬浮窗口
* onTouchEvent触摸事件的处理
* 简单的TranslateAnimation平移动画
* GridView api的熟练使用

原理分析

首先它是一个gridview,里面放了很多item,至于item你可以自己随意布局,例子中我只用了一个textview。

1、触发gridview长按事件,用windowmanager添加一个悬浮view,并占位隐藏原来的item,这个悬浮窗就是我们长按的item的一个图片副本,并且将悬浮view定位到自己的手指触摸点,将这个悬浮view设置放大倍数及透明度;
2、监听手指的移动,实时改变悬浮view的位置;
3、当移动距离超过自己的position时,用TranslateAnimation动画平移从起始位置到目标位置之间的item;
4、当手机抬起时,改变adapter中数据的位置,刷新gridview,并释放悬浮view;

下面我们一步一步的来撸代码

//首先展示一下需要的成员变量private WindowManager.LayoutParams mWindowParams;private WindowManager mWindowManager;//被拖拽的item图片副本private ImageView mDragImageView;//按下时手指的坐标private float downX, downY;//是否正在拖拽private boolean isDraging = false;//是否正在进行移动item动画,防止高频率触发动画而发生抖动private boolean isMoving = false;//被拖拽和未被拖拽的标记private static final int NOT_DRAG_ITEM = 0x0;private static final int SHOW_DRAG_ITEM = 0x1;//拖动时副本放大倍数private static final float DRAG_SCALE = 1.2F;//记录拖拽的item位置private int mDragItemPosition;//记录最后一个item动画toString格式private String lastAnim;   

在构造方法中初始化必要的一些变量:

 public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}private void initView() {//设置item长按事件setOnItemLongClickListener(this);mWindowParams = new WindowManager.LayoutParams();mDragImageView = new ImageView(getContext());//标记未被拖拽mDragImageView.setTag(NOT_DRAG_ITEM);//获取窗口管理对象,用于后面向窗口中添加dragImageViewmWindowManager = (WindowManager)   getContext().getSystemService(Context.WINDOW_SERVICE);}

在长按事件中创建副本,隐藏拖拽item

  @Overridepublic boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {mDragItemPosition = position;//清空item之前的图片缓存view.destroyDrawingCache();//开启图片缓存view.setDrawingCacheEnabled(true);//创建一个item的图片副本Bitmap dragBitmap = Bitmap.createBitmap(view.getDrawingCache());mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;//定义副本的长和宽mWindowParams.width = (int) (DRAG_SCALE * dragBitmap.getWidth());mWindowParams.height = (int) (DRAG_SCALE * dragBitmap.getHeight());//定义副本的位置mWindowParams.x = (int) (downX - dragBitmap.getWidth() / 2);mWindowParams.y = (int) (downY - dragBitmap.getHeight() / 2);//定义副本的附加参数:不能点击、不能获取焦点、悬浮窗的形式mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;//定义副本支持透明格式mWindowParams.format = PixelFormat.TRANSLUCENT;mWindowParams.windowAnimations = 0;//如果之前有这个副本先移除if ((int) mDragImageView.getTag() == SHOW_DRAG_ITEM) {mWindowManager.removeView(mDragImageView);mDragImageView.setTag(NOT_DRAG_ITEM);}//将图片副本放入imageviewmDragImageView.setImageBitmap(dragBitmap);//设置已有副本标记mDragImageView.setTag(SHOW_DRAG_ITEM);//设置imageView透明度mDragImageView.setAlpha(0.7f);//添加这个imageview到悬浮窗mWindowManager.addView(mDragImageView, mWindowParams);//此时状态变为可拖拽isDraging = true;//通知adapter隐藏拖拽的item((DragAdapter) getAdapter()).hideItem(position);//将拖拽item的图片缓存功能关闭,释放内存view.setDrawingCacheEnabled(false);return true;}

监听手指的移动来移动item副本,并执行动画:

@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN://记录按下x、y轴位置downX = ev.getRawX();downY = ev.getRawY();break;case MotionEvent.ACTION_MOVE:if (isDraging) {//移动时实时刷新副本的坐标位置mWindowParams.x = (int) (ev.getRawX() - mDragImageView.getWidth() / 2);mWindowParams.y = (int) (ev.getRawY() - mDragImageView.getHeight() / 2);//改变副本的位置mWindowManager.updateViewLayout(mDragImageView, mWindowParams);//移动中间的itemmoveItem((int) ev.getX(), (int) ev.getY());}break;}

使用平移动画移动中间item的位置

 /*** 移动item的位置*/private void moveItem(int x, int y) {//得到移动到的点x、y在gridview中的所在位置final int currentPosition = pointToPosition(x, y);//如果移动位置移除自己所在的位置,则支持位置移动if (currentPosition != mDragItemPosition && currentPosition > -1) {//如果已经在执行移动动画,则不再执行,防止高频率触发动画而发生抖动if (isMoving) return;//向后拖拽,则中间的item向前移动if (currentPosition > mDragItemPosition) {for (int i = mDragItemPosition+1; i <= currentPosition; i++) {View before = getChildAt(i);int toPosition = i;int toX = -before.getWidth();int toY = 0;if(toPosition % getNumColumns() == 0){toX = before.getWidth() * (getNumColumns()-1);toY = -before.getHeight();}startAnim(before, toX , toY , i ,currentPosition);}} else {     //向前拖拽,则中间的item向后移动for (int i = mDragItemPosition - 1 ; i >= currentPosition ; i--) {View before = getChildAt(i);int toPosition = i+1;int toX = before.getWidth();int toY = 0;if(toPosition % getNumColumns() == 0){toX = - before.getWidth() * (getNumColumns()-1);toY = before.getHeight();}startAnim(before, toX , toY , i ,currentPosition);}}}}/*** 启动平移动画* @param view              将要移动的view* @param toX               x轴上移动值* @param toY               y轴上的移动值* @param positon           当前item的位置* @param currentPosition   拖拽中的view的最终位置*/private void startAnim(View view, float toX , float toY , int positon , final int currentPosition) {Animation anim = getMoveAnim( toX , toY );if (positon == currentPosition) {lastAnim = anim.toString();}view.startAnimation(anim);anim.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {//动画开始将移动状态设为trueisMoving = true;}@Overridepublic void onAnimationEnd(Animation animation) {//最后一个item动画结束,将移动状态设为falseif (!StringUtils.isEmpty(lastAnim) && lastAnim.equals(animation.toString())) {//通知adapter刷新数据的位置,刷新界面((DragAdapter) getAdapter()).changeItemPosition(currentPosition, mDragItemPosition);mDragItemPosition = currentPosition;isMoving = false;}}@Overridepublic void onAnimationRepeat(Animation animation) {}});}/*** 生成item移动动画*/private TranslateAnimation getMoveAnim(float toX, float toY) {TranslateAnimation anim = new TranslateAnimation(0.0F, toX , 0.0F , toY);anim.setDuration(300);anim.setFillAfter(true);return anim;}

手指抬起时,将所有状态还原,释放窗口悬浮view:

 case MotionEvent.ACTION_UP:if (isDraging) {((DragAdapter) getAdapter()).cancelDrag();if ((int) mDragImageView.getTag() == SHOW_DRAG_ITEM) {mWindowManager.removeView(mDragImageView);mDragImageView.setTag(NOT_DRAG_ITEM);}isDraging = false;isMoving = false;}break;

在来看看拖拽GridView专用的adapter基类,主要是提供了一些工具方法:

/*** 拖拽adapter基类,如要使用拖拽gridview必须继承* 继承时不能复用convertView,否则会出现一些奇怪现象*/public static abstract class DragAdapter<T> extends BaseAdapter {protected List<T> datas = new ArrayList<T>();private int hidePosition = AdapterView.INVALID_POSITION;/*** 是否隐藏标记*/public static final int ITEM_TYPE_NORMAL = 0x0;public static final int ITEM_TYPE_HIDE = 0x1;public void setDatas(List<T> list) {this.datas.clear();this.datas.addAll(list);notifyDataSetChanged();}@Overridepublic int getCount() {return datas.size();}@Overridepublic T getItem(int position) {return datas.get(position);}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {return getItemView(position, convertView, parent);}protected abstract View getItemView(int position, View convertView, ViewGroup parent);/*** 返回要隐藏的item位置,在继承这个adapter基类时只要通过这个方法判断type = ITEM_TYPE_NORMAL时将该item隐藏即可*/@Overridepublic int getItemViewType(int position) {if (hidePosition == position) {return ITEM_TYPE_HIDE;}return ITEM_TYPE_NORMAL;}/*** 取消拖拽*/public void cancelDrag() {hidePosition = AdapterView.INVALID_POSITION;notifyDataSetChanged();}/*** 改变拖拽item位置*/public void changeItemPosition(int currentPosition, int dragPosition) {//从后往前移if (currentPosition < dragPosition) {datas.add(currentPosition, getItem(dragPosition));datas.remove(dragPosition + 1);} else {    //从前往后移datas.add(currentPosition + 1, getItem(dragPosition));datas.remove(dragPosition);}hidePosition = currentPosition;notifyDataSetChanged();}/*** 隐藏item*/public void hideItem(int positon) {hidePosition = positon;notifyDataSetChanged();}}

好了,最后贴上GragGridView源码:

/*** Created by caoyujie on 17/1/13.* 可拖拽的gridView*/public class DragGridView<T extends DragGridView.DragAdapter> extends GridView implements AdapterView.OnItemLongClickListener {private WindowManager.LayoutParams mWindowParams;private WindowManager mWindowManager;private float downX, downY;/*** 被拖拽的item图片副本*/private ImageView mDragImageView;/*** 是否正在拖拽*/private boolean isDraging = false;/*** 是否正在进行移动item动画,防止高频率触发动画而发生抖动*/private boolean isMoving = false;/*** 被拖拽和未被拖拽的标记*/private static final int NOT_DRAG_ITEM = 0x0;private static final int SHOW_DRAG_ITEM = 0x1;/*** 拖动时放大倍数*/private static final float DRAG_SCALE = 1.2F;/*** 记录拖拽的item位置*/private int mDragItemPosition;/*** 记录最后一个item动画toString格式*/private String lastAnim;/*** 行间距*/private int verticalSpacing;public DragGridView(Context context) {this(context, null);}public DragGridView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}private void initView() {setOnItemLongClickListener(this);mWindowParams = new WindowManager.LayoutParams();mDragImageView = new ImageView(getContext());//标记未被拖拽mDragImageView.setTag(NOT_DRAG_ITEM);//获取窗口管理对象,用于后面向窗口中添加dragImageViewmWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);verticalSpacing = getVerticalSpacing();}@Overridepublic boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {mDragItemPosition = position;//清空item之前的图片缓存view.destroyDrawingCache();//开启图片缓存view.setDrawingCacheEnabled(true);//创建一个item的图片副本Bitmap dragBitmap = Bitmap.createBitmap(view.getDrawingCache());mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;//定义副本的长和宽mWindowParams.width = (int) (DRAG_SCALE * dragBitmap.getWidth());mWindowParams.height = (int) (DRAG_SCALE * dragBitmap.getHeight());//定义副本的位置mWindowParams.x = (int) (downX - dragBitmap.getWidth() / 2);mWindowParams.y = (int) (downY - dragBitmap.getHeight() / 2);//定义副本的附加参数:不能点击、不能获取焦点、悬浮窗的形式mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;//定义副本支持透明格式mWindowParams.format = PixelFormat.TRANSLUCENT;mWindowParams.windowAnimations = 0;//如果之前有这个副本先移除if ((int) mDragImageView.getTag() == SHOW_DRAG_ITEM) {mWindowManager.removeView(mDragImageView);mDragImageView.setTag(NOT_DRAG_ITEM);}//将图片副本放入imageviewmDragImageView.setImageBitmap(dragBitmap);//设置已有副本标记mDragImageView.setTag(SHOW_DRAG_ITEM);//设置imageView透明度mDragImageView.setAlpha(0.7f);//添加这个imageview到悬浮窗mWindowManager.addView(mDragImageView, mWindowParams);//此时状态变为可拖拽isDraging = true;//通知adapter隐藏拖拽的item((DragAdapter) getAdapter()).hideItem(position);//将拖拽item的图片缓存功能关闭,释放内存view.setDrawingCacheEnabled(false);return true;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN://记录按下x、y轴位置downX = ev.getRawX();downY = ev.getRawY();break;case MotionEvent.ACTION_MOVE:if (isDraging) {//移动时实时刷新副本的坐标位置mWindowParams.x = (int) (ev.getRawX() - mDragImageView.getWidth() / 2);mWindowParams.y = (int) (ev.getRawY() - mDragImageView.getHeight() / 2);//改变副本的位置mWindowManager.updateViewLayout(mDragImageView, mWindowParams);//移动中间的itemmoveItem((int) ev.getX(), (int) ev.getY());}break;case MotionEvent.ACTION_UP:if (isDraging) {((DragAdapter) getAdapter()).cancelDrag();if ((int) mDragImageView.getTag() == SHOW_DRAG_ITEM) {mWindowManager.removeView(mDragImageView);mDragImageView.setTag(NOT_DRAG_ITEM);}isDraging = false;isMoving = false;}break;}return super.onTouchEvent(ev);}/*** 移动item的位置*/private void moveItem(int x, int y) {//得到移动到的点x、y在gridview中的所在位置final int currentPosition = pointToPosition(x, y);//如果移动位置移除自己所在的位置,则支持位置移动if (currentPosition != mDragItemPosition && currentPosition > -1) {//如果已经在执行移动动画,则不再执行,防止高频率触发动画而发生抖动if (isMoving) return;//向后拖拽,则中间的item向前移动if (currentPosition > mDragItemPosition) {for (int i = mDragItemPosition+1; i <= currentPosition; i++) {View before = getChildAt(i);int toPosition = i;int toX = -before.getWidth();int toY = 0;if(toPosition % getNumColumns() == 0){toX = before.getWidth() * (getNumColumns()-1);toY = -before.getHeight() - verticalSpacing;}startAnim(before, toX , toY , i ,currentPosition);}} else {     //向前拖拽,则中间的item向后移动for (int i = mDragItemPosition - 1 ; i >= currentPosition ; i--) {View before = getChildAt(i);int toPosition = i+1;int toX = before.getWidth();int toY = 0;if(toPosition % getNumColumns() == 0){toX = - before.getWidth() * (getNumColumns()-1);toY = before.getHeight() + verticalSpacing;}startAnim(before, toX , toY , i ,currentPosition);}}}}/*** 启动平移动画* @param view              将要移动的view* @param toX               x轴上移动值* @param toY               y轴上的移动值* @param positon           当前item的位置* @param currentPosition   拖拽中的view的最终位置*/private void startAnim(View view, float toX , float toY , int positon , final int currentPosition) {Animation anim = getMoveAnim( toX , toY );if (positon == currentPosition) {lastAnim = anim.toString();}view.startAnimation(anim);anim.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {//动画开始将移动状态设为trueisMoving = true;}@Overridepublic void onAnimationEnd(Animation animation) {//最后一个item动画结束,将移动状态设为falseif (!StringUtils.isEmpty(lastAnim) && lastAnim.equals(animation.toString())) {//通知adapter刷新数据的位置,刷新界面((DragAdapter) getAdapter()).changeItemPosition(currentPosition, mDragItemPosition);mDragItemPosition = currentPosition;isMoving = false;}}@Overridepublic void onAnimationRepeat(Animation animation) {}});}/*** 生成item移动动画*/private TranslateAnimation getMoveAnim(float toX, float toY) {TranslateAnimation anim = new TranslateAnimation(0.0F, toX , 0.0F , toY);anim.setDuration(300);anim.setFillAfter(true);return anim;}/*** 拖拽adapter基类,如要使用拖拽gridview必须继承* 继承时不能复用convertView,否则会出现一些奇怪现象*/public static abstract class DragAdapter<T> extends BaseAdapter {protected List<T> datas = new ArrayList<T>();private int hidePosition = AdapterView.INVALID_POSITION;/*** 是否隐藏标记*/public static final int ITEM_TYPE_NORMAL = 0x0;public static final int ITEM_TYPE_HIDE = 0x1;public void setDatas(List<T> list) {this.datas.clear();this.datas.addAll(list);notifyDataSetChanged();}@Overridepublic int getCount() {return datas.size();}@Overridepublic T getItem(int position) {return datas.get(position);}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {return getItemView(position, convertView, parent);}protected abstract View getItemView(int position, View convertView, ViewGroup parent);/*** 返回要隐藏的item位置,在继承这个adapter基类时只要通过这个方法判断type = ITEM_TYPE_NORMAL时将该item隐藏即可*/@Overridepublic int getItemViewType(int position) {if (hidePosition == position) {return ITEM_TYPE_HIDE;}return ITEM_TYPE_NORMAL;}/*** 取消拖拽*/public void cancelDrag() {hidePosition = AdapterView.INVALID_POSITION;notifyDataSetChanged();}/*** 改变拖拽item位置*/public void changeItemPosition(int currentPosition, int dragPosition) {//从后往前移if (currentPosition < dragPosition) {datas.add(currentPosition, getItem(dragPosition));datas.remove(dragPosition + 1);} else {    //从前往后移datas.add(currentPosition + 1, getItem(dragPosition));datas.remove(dragPosition);}hidePosition = currentPosition;notifyDataSetChanged();}/*** 隐藏item*/public void hideItem(int positon) {hidePosition = positon;notifyDataSetChanged();}}
}/*** Created by caoyujie on 17/1/13.* 拖拽adapter的实现类* 我们实际使用的adapter*/public class MenuDragAdapter extends DragGridView.DragAdapter {private LayoutInflater mLayoutInflater;public MenuDragAdapter(Context context) {mLayoutInflater = LayoutInflater.from(context);}@Overrideprotected View getItemView(int position, View convertView, ViewGroup parent) {convertView = mLayoutInflater.inflate(R.layout.list_menu_drag, parent, false);TextView label = (TextView) convertView.findViewById(R.id.tv_label);label.setText((String) datas.get(position));return convertView;}
}

通过这个view我们可以学到很多的知识,值得我们去探索,想看源码的可以参考我的开源项目:https://github.com/18973809797/BaseStorehouse。
全局搜索 DragGridView即可,效果在主页点击标题进入:
里面还有很多实用的app框架实现及实用view的学习哦~

可拖拽排序的GridView(高仿今日头条编辑频道效果)相关推荐

  1. IOS每日精选源码,边缘识别导航条管理高仿今日头条语音查询汇率源码

    CIDetector边缘识别 超级简单的导航条管理工具EasyNavigation navigationbar 高仿今日头条频道选择弹框 iOS一个比较实用的侧边栏管理器 联动tableView,菜单 ...

  2. android如何展示富文本_android高仿今日头条富文本编辑(发布文章)

    前言: 在经历了几个月的项目期限.我们遇到了前端发布文章,要用到富文本编辑的功能.在一番衡量下最终用到了richeditor-android第三方框架.实现原理就是通过webView和js实现前端富文 ...

  3. android 上下滚动文字_android高仿今日头条富文本编辑(发布文章)

    前言 在经历了几个月的项目期限.我们遇到了前端发布文章,要用到富文本编辑的功能.在一番衡量下最终用到了[richeditor-android](https://github.com/wasabeef/ ...

  4. android 仿写开发者头条,android高仿今日头条富文本编辑(发布文章)

    前言: 在经历了几个月的项目期限.我们遇到了前端发布文章,要用到富文本编辑的功能.在一番衡量下最终用到了richeditor-android第三方框架.实现原理就是通过webView和js实现前端富文 ...

  5. android 今日头条加载动画,高仿今日头条加载动画

    01 每每浏览手机app时,发现有的效果体验不错,作为一位程序员,总想要是自己来做,怎么实现. 今天我们来模仿今日头条的加载动画. 首先我们来看一下我们这个demo最终效果,有图有真相. 高仿今日头条 ...

  6. Android 仿今日头条首页标题栏效果

    今天带来的是仿今日头条首页的联动滑动效果,废话不多说,先上效果图: 思路: 做这个我们需要实现的效果有 1.滑动内容区域,标题栏会有变化来显示当前所处的位置. 2.点击标题栏,内容区域也会随着滑动并跳 ...

  7. android高仿今日头条 --新闻阅读器

    摘要: 开发流程 第一篇:(android高仿系列)今日头条 --新闻阅读器 (一) 涉及到的知识点有 1.slidingmenu.lib  (侧拉菜单包)   使用方法配置以及下载:点击这里   实 ...

  8. android高仿今日头条小视频转场切换效果

    可以先看看今日头条效果 功能分析 点击列表上的一个item,该item会放大,最后直接全屏播放小视频,刚开始看上去,以为是个共享元素的转场动画, 后来想到,共享元素要在android 5.0以上支持, ...

  9. Android 仿今日头条的频道管理

    //主布局 activity_main.xml<?xml version="1.0" encoding="utf-8"?> <Relative ...

最新文章

  1. 零基础学Python-爬虫-5、下载网络视频
  2. Java黑皮书课后题第4章:*4.6(图上的随机点)编写一个程序,产生一个圆心位于(0,0)原点半径为40的圆上面的三个随机点,显示由这三个随机点组成的三角形的三个角的度数
  3. 你以为这样写代码很6,但我看不懂
  4. 20145321 实验三实验报告
  5. Linux和window写python,搭建Python环境(window和linux)
  6. CSS 权威指南 读书笔记(三)
  7. HTTP协议的工作原理
  8. Unity-背包系统与Json文本解析
  9. 另外一台电脑打开html,有的网页你打不开,在别的电脑就能打开,这样处理就解决了...
  10. 动词ing形式的5种用法_动词ing形式的用法及变化规则 | 学思外教
  11. Exchange 日常管理六之:创建邮箱数据库
  12. lisp实心圆点怎么画_cad中怎么样画实心圆点
  13. 基于TensorFlow Encrypted (TFE)的隐私计算benchmark
  14. 4PCS、super4PCS粗配准算法理解
  15. sap crm行业解决方案_培训机构行业crm系统解决方案
  16. Android接收短信和发送短信
  17. 怎样在线快速缩小动图大小?怎样在线压缩gif图片?
  18. Nuxt在SPA模式下的鉴权处理(1)
  19. c++调用动态库失败解决办法
  20. 接口测试-如何测试需要登录的接口

热门文章

  1. HDU 6656 Kejin Player
  2. 在计算机网络中wm表示什么网,网络基础知识
  3. 要知道宇宙有多少星球,比数清地球上的沙子数量还要困难!
  4. 《软件开发本质论》笔记——了解价值,然后从可能去做的所有事情中选择那些最重要的去做
  5. 了解如何通过简单的技巧在Photoshop或GIMP中制作HDR图像
  6. html怎么绑定数据,06、如何在html中绑定数据
  7. 小技巧---笔记本外接显示器设置全屏壁纸
  8. html5制作奥运五环,第一讲:使用html5——canvas绘制奥运五环
  9. Android中HTTPS之一(三)具体操作(代码实现)
  10. c语言中简易公交一卡通系统的实现