取代jQuery?

我很久之前便听说了angularJS的大名,之前的leader也经常感叹angularJS的设计如何如何精妙,可叹一直没有机会深入了解,国庆长假因为没钱出游,倒是可以对他做一个了解......

根据之前的经验,就现有的前端项目,如果最初没有良好的设计,做到一定阶段一定会变得难以维护,就算最初有设计,变化无常的PM也会让你的项目BUG丛生。

一个页面的复杂程度不断的增加,依赖模块也会变得混乱,而其中最为头疼的就是页面级随心所欲的DOM操作了!

MVC类的框架可以很好的解决以上问题,而号称MVVM的angularJS在处理这种情况似乎更有话语权,所以我们今天便来好好研究其一番。

angular适合做具有复杂数据交互的前端应用,他旨在让我们摆脱繁琐的DOM操作,而将注意力集中在业务逻辑上,这里摆脱繁琐的DOM操作是个非常关键的愿景,也是很多人不太理解,甚至会将jQuery这种库与Backbone或者angularJS这种框架做对比的原因。

jQuery是非常优秀的DOM操作工具库,在DOM操作上,基本没有库能超越他了
但Backbone&angularJS这种MVC是框架提供的是完整的解决方案,甚至会依赖jQuery&zepto,他们是两个东西,不能互相比较,所以完全没有angularJS要取代jQuery的可能,而当DOM操作过于杂乱一定是你的项目出了问题。

这里举个jQuery不依赖MVC骨架的例子,我们的订单填写页,需要在商品数量变化后导致金额变化,并且没有选商品时,支付按钮不可点击:

对于一个有些经验的菜鸟来说,可能会这样写代码:

$('#reduceNum').click(function() {
$('#payBar #num').text($('#curNum').html() - 1);
});

对于一些有一定经验的老鸟来说,可能会这样写代码:

 events: {
'click #reduceNum': reduceNumAction
}, reduceNumAction: function() {
  $('#payBar #num').text($('#curNum').html() - 1);
}

第一段代码可能会导致你年底加薪无望,并且在团队中没有话语权;而第二段代码积累到一定量后会让这个项目变得不可维护:

① 支付工具栏初始化状态如何显示,如果数字组件按需做异步加载,这个显示将变得更加负责。

② 哪些操作将导致支付栏变化,你如何组织这些变化的代码,是让他四散到各处,还是集合在一起,集合后导致函数过大怎么办?

③ 新增的导致工具栏变化的操作会不会对原来的操作造成影响,新增的代码放在何处?

④ 如果有地方要使用工具栏处的信息,取的信息会不会是无效的(取的时候可能正在变化),应该通过DOM取还是内存取?

⑤ 如果支付栏DOM结构如果变化,对你的程序影响有多大,如何主流程的影响,比较支付点击后只需要操作数据,不需要关注DOM?

⑥ ......

这个就是仅仅依赖jQuery要面临的问题,并且这种问题是无解的,因为这里的专注点是DOM操作而不是数据,如果将关注点变成了数据,代码就不是这样写的,DOM操作仅仅是过程而不是目的,我们代码的目的,往往是展示数据、获取数据,这点一定要清晰。

所以让我们带着这些问题:angular的优势在何处,他如何改善我们的编程体验,进入今天的学习吧。

初探angularJS

Hello World

学习任何一门语言,Hello world是必不可少的,他是我们迈向精通的唯一路径:

 <!doctype html>
<html ng-app>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body>
Hello {{'World'}}!
</body>
</html>

被{{}}包裹的便是angularJS变量,上述程序稍作改变的话:

 <!doctype html>
<html ng-app>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body>
<input ng-model="name" type="text" />
Hello {{name}}!
</body>
</html>

便会同步显示文本框输入内容,这里通信的基础是model对应着ng-model,只要被ng-app包裹就会受angularJS控制,用angularJS自己的话说:HTML标签增强

作用域

为什么文本框中的变化会体现在外层,这个涉及到了ng-model的双向绑定知识,我们暂时不予理睬,但是外层又是从哪里读取name这个变量的呢?

在angular中,属性会存储在一个@scope(作用域)的对象上,每次我们对文本框的更新皆会通知$scope上的name属性,在angular中,$scope是连接controllers(控制器)与template(视图)的主要胶合器。

上述代码完全不涉及js代码,真实的场景中每个代码段会对controller做依赖,我们这里对代码做一些更改:

 <!doctype html>
<html>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body ng-app="app" ng-controller="MainCtrl">
<h1 ng-click="click()">
Hello {{name}}!
</h1>
<script>
var app = angular.module('app', []);
app.controller('MainCtrl', function ($scope) {
$scope.name = 'World';
$scope.click = function () {
$scope.name = '霹雳布袋戏';
};
});
</script>
</body>
</html>

这里首先定义了一个application模块,后续会看见,我们每次代码一定会新建一个application,相当于命名空间的意思,后面还可以做依赖用。

接着,我们创建了一个controller模块,这里已经有点MVC的味道了,controller接受$scope属性,这个时候模板上所有子标签对这个控制器中的属性便有了访问权限,这里用到了一些angular指令

ng-app:告诉html标签已经处于angular的控制了,可以使用angular的特性
ng-controller:一个module下面可以包括多个控制器,每一个标签所属的控制器由该指令指定

上述代码是将控制器中的数据读出来,我们同样也可以将View中的数据读入到控制器:

 <!doctype html>
<html>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body ng-app="app" ng-controller="MainCtrl">
<input type="text" ng-model="message" />
<h1 ng-click="click()">
Hello {{name}}!
</h1>
<script>
var app = angular.module('app', []);
app.controller('MainCtrl', function ($scope) {
$scope.name = 'World';
$scope.click = function () {
$scope.name = $scope.message;
};
});
</script>
</body>
</html>

PS:看到这里,老夫虎躯为之一振,对该特性的实现产生了兴趣,后续值得深入

指令

指令让我们有能力使用angular规定的方式为HTML标签增加新特性,angular内置了很多有用的指令,这里仍然举一个简单的例子说明问题:

 <!doctype html>
<html>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body ng-app="app">
<ul ng-controller="MainCtrl">
<li ng-repeat="v in arr">{{v}}</li>
</ul>
<script>
var app = angular.module('app', []);
app.controller('MainCtrl', function ($scope) {
$scope.arr = ['素还真', '一页书', '叶小钗']
});
</script>
</body>
</html>

我们除了使用angular的内置指令外,还可以自定义指令,比如这里的让文本框自动获取焦点的指令:

 <!doctype html>
<html>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body ng-app="app" ng-controller="MainCtrl">
<input type="text" focus ng-model="user.name" />
<button ng-click="greet()">
Click here!</button>
<h3>
{{ message }}</h3>
<script>
var app = angular.module('app', []);
app.controller('MainCtrl', function ($scope) {
$scope.greet = function () {
$scope.message = "Hello, " + $scope.user.name;
}
});
app.directive('focus', function () {
return {
link: function (scope, element, attrs) {
element[0].focus();
}
};
});
</script>
</body>
</html>

指令的使用可以很复杂,后续我们会更加深入,这里再举一个单独使用的例子:

 <!doctype html>
<html>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body ng-app="app">
<hello></hello>
<script>
var app = angular.module('app', []);
app.directive('hello', function () {
return {
restrict: "E",
replace: true,
template: "<div>显示固定数据,类似自定义标签</div>"
}
});
</script>
</body>
</html>

指令的定义有很多参数,可以指定该指令作为属性还是作为标签,这个我们后续再深入了解。

过滤器

感觉过滤器是参考的smarty的语法,一般而言是用作显示的增强,angular本身也提供了很多内置过滤器,比如:

 {{ "aaaa" | uppercase }} // AAAA
{{ "BBBB" | lowercase }} // bbbb

感觉比较有用的是日期操作过滤器:

{{ 1427345339072 | date:'yyyy' }} //
{{ 1427345339072 |date:'MM' }} //
{{ 1427345339072 | date:'d' }} // 26,一月中第多少天
......

数字格式化:

{{12.13534|number:2}} // 12.14 四舍五入保留两位小数
{{10000000|number}} // 10,000,000

当然,我们可以使用自定义过滤器,比如这里我想对超出某一区间的数字加...

 <!doctype html>
<html>
<head>
<script src="angular.js" type="text/javascript"></script>
</head>
<body ng-app="app" ng-controller="MainCtrl">
<input type="text" ng-model="message" />
<h3>
{{ message |myFilter }}</h3>
<script>
var app = angular.module('app', []);
app.controller('MainCtrl', function ($scope) {
$scope.message = '';
}); app.filter('myFilter', function () {
return function (input, param) {
return input.length < 5 ? input : input.substring(0, 5) + '...'
}
});
</script>
</body>
</html>

具备了以上知识,我们尝试进入To都MVC看看

参考:http://www.cnblogs.com/whitewolf/p/angularjs-start.html

TodoMVC

我们由最新的TodoMVC下载代码:http://todomvc.com/,首先查看js引用情况:

 <script src="node_modules/angular/angular.js"></script>
<script src="node_modules/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todoCtrl.js"></script>
<script src="js/services/todoStorage.js"></script>
<script src="js/directives/todoFocus.js"></script>
<script src="js/directives/todoEscape.js"></script>

除了angular本体文件外,还多了个angular的扩展,做单页应用的路由功能的,这个路由代码量不大,使用和Backbone的路由比较类似;app.js为入口文件,配置路由的地方;余下是控制器文件文件以及一个localstorage的操作服务,余下就是指令了。

代码首先定义了一个模块作为本次程序的命名空间:

 angular.module('todomvc', ['ngRoute'])

ngRoute为其依赖项,可以从route的定义看出:

 var ngRouteModule = angular.module('ngRoute', ['ng']).
provider('$route', $RouteProvider),
$routeMinErr = angular.$$minErr('ngRoute');

这里来看看其router的配置,以及index.html的写法:

 <!doctype html>
<html lang="en" data-framework="angularjs">
<head>
<meta charset="utf-8">
<title>AngularJS • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
<style>[ng-cloak] { display: none; }</style>
</head>
<body ng-app="todomvc">
<ng-view /> <script type="text/ng-template" id="todomvc-index.html">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="addTodo()">
<input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" ng-disabled="saving" autofocus>
</form>
</header>
<section id="main" ng-show="todos.length" ng-cloak>
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusFilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedTodo}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="todo.completed" ng-change="toggleCompleted(todo)">
<label ng-dblclick="editTodo(todo)">{{todo.title}}</label>
<button class="destroy" ng-click="removeTodo(todo)"></button>
</div>
<form ng-submit="saveEdits(todo, 'submit')">
<input class="edit" ng-trim="false" ng-model="todo.title" todo-escape="revertEdits(todo)" ng-blur="saveEdits(todo, 'blur')" todo-focus="todo == editedTodo">
</form>
</li>
</ul>
</section>
<footer id="footer" ng-show="todos.length" ng-cloak>
<span id="todo-count"><strong>{{remainingCount}}</strong>
<ng-pluralize count="remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
</span>
<ul id="filters">
<li>
<a ng-class="{selected: status == ''} " href="#/">All</a>
</li>
<li>
<a ng-class="{selected: status == 'active'}" href="#/active">Active</a>
</li>
<li>
<a ng-class="{selected: status == 'completed'}" href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" ng-click="clearCompletedTodos()" ng-show="completedCount">Clear completed</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Credits:
<a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>,
<a href="http://ericbidelman.com">Eric Bidelman</a>,
<a href="http://jacobmumm.com">Jacob Mumm</a> and
<a href="http://igorminar.com">Igor Minar</a>
</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</script>
<script src="node_modules/angular/angular.js"></script>
<script src="node_modules/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todoCtrl.js"></script>
<script src="js/services/todoStorage.js"></script>
<script src="js/directives/todoFocus.js"></script>
<script src="js/directives/todoEscape.js"></script>
</body>
</html>

index.html

 var routeConfig = {
controller: 'TodoCtrl',
templateUrl: 'todomvc-index.html',
resolve: {
store: function (todoStorage) {
// Get the correct module (API or localStorage).
return todoStorage.then(function (module) {
module.get(); // Fetch the todo records in the background.
return module;
});
}
}
}; $routeProvider
.when('/', routeConfig)
.when('/:status', routeConfig)
.otherwise({
redirectTo: '/'
});

这个代码现在基本看不懂,大概意思应该就是根据路由执行config中的逻辑,将模板展示在页面上,其中index.html有一段代码应该是用于替换模板的:

<ng-view />

我们先抛开那段看不懂的,直奔主流程,目光聚焦到控制器controller:

 angular.module('todomvc')
.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, $filter, store) {
'use strict'; var todos = $scope.todos = store.todos; $scope.newTodo = '';
$scope.editedTodo = null; $scope.$watch('todos', function () {
$scope.remainingCount = $filter('filter')(todos, { completed: false }).length;
$scope.completedCount = todos.length - $scope.remainingCount;
$scope.allChecked = !$scope.remainingCount;
}, true); // Monitor the current route for changes and adjust the filter accordingly.
$scope.$on('$routeChangeSuccess', function () {
var status = $scope.status = $routeParams.status || '';
$scope.statusFilter = (status === 'active') ?
{ completed: false } : (status === 'completed') ?
{ completed: true } : {};
}); $scope.addTodo = function () {
var newTodo = {
title: $scope.newTodo.trim(),
completed: false
}; if (!newTodo.title) {
return;
} $scope.saving = true;
store.insert(newTodo)
.then(function success() {
$scope.newTodo = '';
})
.finally(function () {
$scope.saving = false;
});
}; $scope.editTodo = function (todo) {
$scope.editedTodo = todo;
// Clone the original todo to restore it on demand.
$scope.originalTodo = angular.extend({}, todo);
}; $scope.saveEdits = function (todo, event) {
// Blur events are automatically triggered after the form submit event.
// This does some unfortunate logic handling to prevent saving twice.
if (event === 'blur' && $scope.saveEvent === 'submit') {
$scope.saveEvent = null;
return;
} $scope.saveEvent = event; if ($scope.reverted) {
// Todo edits were reverted-- don't save.
$scope.reverted = null;
return;
} todo.title = todo.title.trim(); if (todo.title === $scope.originalTodo.title) {
$scope.editedTodo = null;
return;
} store[todo.title ? 'put' : 'delete'](todo)
.then(function success() {}, function error() {
todo.title = $scope.originalTodo.title;
})
.finally(function () {
$scope.editedTodo = null;
});
}; $scope.revertEdits = function (todo) {
todos[todos.indexOf(todo)] = $scope.originalTodo;
$scope.editedTodo = null;
$scope.originalTodo = null;
$scope.reverted = true;
}; $scope.removeTodo = function (todo) {
store.delete(todo);
}; $scope.saveTodo = function (todo) {
store.put(todo);
}; $scope.toggleCompleted = function (todo, completed) {
if (angular.isDefined(completed)) {
todo.completed = completed;
}
store.put(todo, todos.indexOf(todo))
.then(function success() {}, function error() {
todo.completed = !todo.completed;
});
}; $scope.clearCompletedTodos = function () {
store.clearCompleted();
}; $scope.markAll = function (completed) {
todos.forEach(function (todo) {
if (todo.completed !== completed) {
$scope.toggleCompleted(todo, completed);
}
});
};
});

这段代码130行不到,让我体会到了深深的神奇,首先我们在app中返回了读取到localstorage的对象:

 resolve: {
store: function (todoStorage) {
// Get the correct module (API or localStorage).
return todoStorage.then(function (module) {
module.get(); // Fetch the todo records in the background.
return module;
});
}
}

然后就在controller的依赖项中读到了被注入的对象:

var todos = $scope.todos = store.todos;

此时,模板也被插到了页面上,等待controller的执行:

首先这里有一个$watch方法,监控着todos的变化,每次变化都会体现到这里,导致view的变化:

 $scope.$watch('todos', function () {
$scope.remainingCount = $filter('filter')(todos, { completed: false }).length;
$scope.completedCount = todos.length - $scope.remainingCount;
$scope.allChecked = !$scope.remainingCount;
}, true);

然后我们将关注点放在新增项目上:

 $scope.addTodo = function () {
var newTodo = {
title: $scope.newTodo.trim(),
completed: false
}; if (!newTodo.title) {
return;
} $scope.saving = true;
store.insert(newTodo)
.then(function success() {
$scope.newTodo = '';
})
.finally(function () {
$scope.saving = false;
});
};

View上的调用点是:

 <header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="addTodo()">
<input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" ng-disabled="saving" autofocus>
</form>
</header>

首先这段代码中有一个autofocus的指令,没有什么卵用:

 angular.module('todomvc')
.directive('todoFocus', function todoFocus($timeout) {
'use strict'; return function (scope, elem, attrs) {
scope.$watch(attrs.todoFocus, function (newVal) {
if (newVal) {
$timeout(function () {
elem[0].focus();
}, 0, false);
}
});
};
});

可以看到model直接绑定到了该文本框上,所以addTodo方法可以直接根据$scope获取文本框的属性,完了调用单例store提供的静态方法存储数据,saving参数可以暂时将文本框变成不可编辑状态,而后todo数据更新,会自动引发View变化,于是流程结束!!!

我们如果将$scope放到全局上对其数据造成变化:

window.sss = $scope;
//控制台中造成变化
sss.todos.pop()

每次返回操作视图时候,该变化会马上反应到View上,于是我发现了以下不同:

① 因为所有与业务相关的数据全部做了双向绑定,我根本没有必要由dom获取数据了,我自然而然的到$scope中获取数据,不知道为什么,这个特性让我有点愉悦!

② 我要做的事情其实就是约定好数据对象,然后将该对象放到要用到的所有视图上即可,每次内存中数据变化Dom会同步更新

于是通过以上两点,我似乎得到了一个惊人的结论:

似乎我一旦配置好ng-model后,我要做的事情仅仅是操作$scope上的数据!!!

因为,前端要做的事情只不过是正确的展示服务器端的数据,每次DOM事件造成的改变也往往是数据引起的,如果我们能做到数据变化自动更新到DOM变化的话,那么DOM操作的必要似乎没有了,而angular干的事情正是如此!!!

思考

到此为止,TodoMVC的代码我虽然没有完全看懂,但是他带给我的震撼是全方位的,之前使用MVC类框架可以规范数据到DOM的操作,很大程度上解除DOM和JavaScript的耦合关系,而angular似乎完全抛开了业务数据导致的DOM变化操作!!!

我们现在团队有一mis后台系统,我在考虑是否要把它接过来,使用angular+bootstrap重构,可能别有一番风味吧!

最后,今天初步调研了一下angularJS,就已经感受到他的魅力了,后面时间需要将之用于实践,并且对其设计思想作深入研究!!!

摆脱DOM操作,从TodoMVC看angularJS的更多相关文章

  1. 从DOM操作看Vue&React的前端组件化,顺带补齐React的demo

    前言 接上文:谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo 上次写完博客后,有朋友反应第一内容有点深,看着迷迷糊糊:第二是感觉没什么使用场景,太过业务化,还不如直接写Vue ...

  2. React.js 小书 Lesson3 - 前端组件化(二):优化 DOM 操作

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson3 转载请注明出处,保留原文链接和作者信息. 看看上一节我们的代码,仔细留意一下 change ...

  3. 前端组件化(二):优化 DOM 操作

    看看上一节我们的代码,仔细留意一下 changeLikeText 函数,这个函数包含了 DOM 操作,现在看起来比较简单,那是因为现在只有 isLiked 一个状态.由于数据状态改变会导致需要我们去更 ...

  4. angularJS之使用指令封装DOM操作

    angularJS之使用指令封装DOM操作 创建指令 指令也是一种服务,只是这种服务的定义有几个特殊要求: 必须使用模块的directive()方法注册服务 必须以对象工厂/factory()方法定义 ...

  5. [译]AngularJS中DOM操作

    再翻译一篇干货短文,原文:AngularJS jQuery 虽然Angularjs将我们从DOM的操作中解放出来了,但是很多时候我们还是会需要在controller/view加载之后执行一些DOM操作 ...

  6. webform(九)——JQuery基础(选择器、事件、DOM操作)

    JQuery -- 一个js函数包 一.选择器 1.基本选择器 ①id选择器:#       ②class选择器:.       ③标签名选择:标签名 ④并列选择:用,隔开          ⑤后代选 ...

  7. 解密jQuery内核 DOM操作的核心函数domManip

    domManip是什么 dom即Dom元素,Manip是Manipulate的缩写,连在一起就是Dom操作的意思. .domManip()是jQuery DOM操作的核心函数 对封装的节点操作做了参数 ...

  8. 解密jQuery内核 DOM操作的核心buildFragment

    文档碎片是什么 http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-B63ED1A3 DocumentFragment is a & ...

  9. 解密jQuery内核 DOM操作

    jQuery针对DOM操作的插入的方法有大概10种 append.prepend.before.after.replaceWith appendTo.prependTo.insertBefore.in ...

随机推荐

  1. 敏捷转型历程 - Sprint4 回顾会

    我: Tech Leader 团队:团队成员分布在两个城市,我所在的城市包括我有4个成员,另外一个城市包括SM有7个成员.另外由于我们的BA离职了,我暂代IT 的PO 职位.PM和我在一个城市,但他不 ...

  2. Linux 权限设置chmod

    Linux中设置权限,一般用chmod命令 1.介绍 权限设置chmod 功能:改变权限命令.常用参数: 1=x(执行权execute) 2=w(写权write) 4=r(读权Read) setuid ...

  3. SpringMvc中的数据校验

    SpringMvc中的数据校验 Hibernate校验框架中提供了很多注解的校验,如下: 注解 运行时检查 @AssertFalse 被注解的元素必须为false @AssertTrue 被注解的元素 ...

  4. .NET基础拾遗(3)字符串、集合和流

    Index: (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基础 ...

  5. Windbg Extension NetExt 使用指南 【3】 ---- 挖掘你想要的数据 Managed Heap

    摘要 : NetExt中有两个比较常用的命令可以用来分析heap上面的对象. 一个是!wheap, 另外一个是!windex. !wheap 这个命令可以用于打印出heap structure信息. ...

  6. Entity Framework 6 Recipes 2nd Edition(9-5)译->删除一个断开的实体

    9-5. 删除一个断开的实体 问题 我们要把一个把WCF上取回的对象做上删除的标志. 解决方案 假设我们有如Figure 9-5所示实体的支付与票据的模型. Figure 9-5. 一个支付与票据的模 ...

  7. Windows.document

    一.找到元素: document.getElementById("id");根据id找,最多找一个 var a =document.getElementById("id& ...

  8. [转载]从MyEclipse到IntelliJ IDEA-让你摆脱鼠标,全键盘操作

    从MyEclipse转战到IntelliJ IDEA的经历 注转载址:http://blog.csdn.net/luoweifu/article/details/13985835 我一个朋友写了一篇“ ...

  9. Android-RecyclerView

    众所周知,RecyclerView是Google公司推出的V7包中的一个重要的控件,非常方便,可以替代现有的ListView和Gridview等控件,它功能很强大,灵活性好,扩展性强,还自带VIewH ...

  10. 关于项目中下单流程HTML设计的一些思考

    需求 上面文字和圈圈是对齐的. 想法 一开始是想把文字和圈圈分开来的,也就是两个盒子放置.但操作中发现,想把它们对齐非常的难,总有一些是无法对齐的. 最终换了一种实现方式,按照需求,不就是想把它们关联 ...