[转载]如何使用Omnifocus做时间管理 3 职场新人 | 小强的时间管理博客

mikel阅读(1043)

[转载]如何使用Omnifocus做时间管理 3 职场新人 | 小强的时间管理博客.

上一篇文章和大家分享了清空Omnifocus的收集篮,这一篇借用小沈的故事聊一聊职场新人如何使用Omnifocus管理行动

故事的开始是这样的:

“小沈在一家汽车零部件制造企业做人事专员,他刚刚工作2年,再加上公司规模大、事情杂,使得他总有种心力交瘁的感觉。

小沈每天早晨都不想起床,因为一睁开眼睛就在想今天有多少件事情在等着他,越想越多,越想越不想起床

到公司以后就看见大家都忙忙碌碌,在巨大的办公室里穿梭,简单和同事打个招呼之后坐下打开电脑,通常这时候桌上的电话就响了,经理开始安排今天的工作,同事们也通过电话、通过喊话、通过邮件、通过便利贴交代给他一些要做的事情,哎,谁让咱是职场新人呢,累死累活的处理杂事。

时不时的还有突然要开的会议,突然要购买的东西,突然要邮寄的资料,突然要安排的面试要去应付……

小沈每天的工作状态都是手忙脚乱,就像玩砸鼹鼠游戏一样。”

image

其实小沈要Hold住他的工作并不难,因为他的事情虽然杂,但是相对都比较明确,比如整理资料、做ppt、邮寄包裹等等

如果他建立一套自己的行动管理系统,提高做事的效率,就不会自乱阵脚。

一共4个步骤,用一张图就可以表达出来:

NewImage

1、清空大脑

通常来讲,职场新人的事情是最多最琐碎的,从经理往上应该事情会越来越少,也越来越重要。

我们想象一棵树,董事长应该是树的主干,他决定方向,就做这一件事即可,总监、经理像是一级、二级树枝,有若干管理项目,但还是相对比较少,底下员工就像是最末端的树叶,事无巨细、种类繁多。

Image fixed

所以我们要先给大脑做大扫除,让它有足够的空间来容纳新的事情。

做大扫除的方法很简单,就是花20分钟的时间,把你脑袋里装的所有的事情,包括工作和生活的,全部写到一张A4纸上

可是大部分的人却做不到!

很多时候因为缺少一个框架的引导,关于这个DavidAllen在《搞定1》里面把这个叫“引子”清单(Trigger list),我觉得那个不大适合我,我分享自己用过的给大家:

点击这里下载

屏幕快照 2013 11 15 上午11 03 30

如果您觉得还有可以完善的地方,欢迎邮件我NewImage

2、养成随时收集的习惯

大脑擅长思考,不擅长记忆。我刚进入职场的时候,总是把所有的事情都装到大脑里,并且以此为傲,每当经理问我一件事情的进度或者一个资料的内容细节时,如果我能立即报出来,会相当得意,后来才知道,我原来做的是最没有技术含量的事情。

不仅如此,我还发现:思考会让大脑活化,记忆会让大脑崩溃

养成随时收集的习惯要觉察到“移动靶”,然后抠动“收集的扳机”。

举例:

【移动靶】同事说:我们明天下午2点去客户那里 — 【抠动扳机】

【移动靶】老板说:帮我准备一下资料 — 【抠动扳机】

【移动靶】老婆说:这周六暖气加压试水 — 【抠动扳机】

再加大点难度

【移动靶】同事说:老板好像今天心情不大好 — 【抠动扳机】手头的方案明天再送

【移动靶】老板说:你到公司3个月了,感觉怎么样? — 【抠动扳机】写工作汇报,加强和老板的沟通

【移动靶】老婆说:最近好无聊啊! — 【抠动扳机】检视是不是和老婆在一起时间太少?买电影票

有意思的地方是:对所有人来说,“移动靶”都是一样的,但是否会“抠动扳机”以及射出什么“子弹”却各不相同,于是人和人就有了成绩上的不同

如果你想养成随时收集的习惯,就会发现经常要和omnifocus的这个界面打交道:

屏幕快照 2013 11 15 上午11 57 04 21

解释一下每一项的用途:

  • 杂事内容:如果功力够深的话,可以直接输入行动,“经理让跟进项目进度”是杂事,“给客户打电话”是行动
  • 归属项目和情境: 这件事归属哪个项目?如果是行动的话,它归属哪个情境?Omnifocus设置里有个选项(在“数据”选项卡里):“整理包含以下内容的收件箱项”,我通 常设置的是“项目或上下文”,下面“单个行动预设列表”我设置的是“行动清单”,这样的话,任何一个收件箱里的事情,包括收集的时候,只要你设置了归属项 目或者情境,它都会自动跳过收件箱而直接进入行动清单。对于比较熟悉GTD的人很有帮助屏幕快照 2013 11 15 下午12 11 09
  • 截止日期:可以点那个小箭头选择日期,我也发现了一些快捷的输入方法(我是全键盘流),比如
    • 可以直接输入中文“今天”“明天”“昨天”但“后天”不行
    • 如果截止日期是当月,比如2013年11月,直接输入日期即可,比如输入“21”,则默认是“13年11月21日”
    • 如果截止日期是今天以后的某天,比如今天是2013年11月15日,你想输入截止日期2013年12月2日,则直接输入“12-2”即可,输入“2-2”则是2014年2月2日
  • 旗标:不能通过tab切换,快捷键是cmd+shift+L,表示需要重点关注
  • 备注:不能通过tab切换,快捷键是cmd+,可以展开,添加富文本,网页上的照片什么的都可以直接复制过去,如果从邮件导入到收件箱会保留原先的链接,也可以附加文件,这些都很棒,因为这意味着你可以把你解决这件事情所需要的资料全部放到备注里,很方便,逻辑上很优雅。
  • 设置:可以自定义一些收集窗口的字段。

这些是桌面端的收集窗口,iOS端更加强大:

  • 重复:比如每周都要写工作汇报,设置重复之后就省事啦
  • 拍摄照片:比如你看到街边广告栏里的一段文字激发了你的一个想法,立刻拍下来,有利于你加工灵感
  • 录音:走路的时候突然冒出一些朦胧的、无法文字话的想法,你可以对着手机录下啰嗦的描述,然后回家后再做思考和整理,也可以试着录课,我录制了15分钟没有问题,更长的还没有试过。

照片

有了桌面、有了手机、有了云,可以满足你随时随地收集杂事的习惯了吧?

 

3、批次处理提高效率

小沈日常事务的特点是:杂、多并且多半都是行动,所以用批次处理的方法提高效率是再合适不过了。

一共分两步:

  • 为行动设置情境(也就是上下文)
  • 点击“上下文”标签进入批次处理状态

屏幕快照 2013 11 15 上午11 57 04 6

屏幕快照 2013 11 15 下午2 12 58 2

需要注意的几个原则:

  • 上下文不要设置太多:我并不是把所有的情境都做成上下文,而是把我最需要批次处理的做成上下文,比如说@外出,@电话做成上下文,@笔记本或@飞机就不会做成上下文。
  • 不一定只有环境可以设置上下文:比如我可以根据keyman设置上下文,@老板,@客户A,也可以把@思考 设置为上下文
  • 每个人不一样,别照抄:有些人比较适合的上下文是西城区、朝阳区、海淀区,没错,他是一位北京的销售员,你懂了?

4、建立稳定工作流

流程化和系统化才能把我们解放出来,所以,不妨建立你的工作流,不管你是什么职位、有多少工作经验。

我刚刚开始学习时间管理的时候就用omnifocus,那时候的工作流是:

  • 旗标:每天最多三个,一有空就想办法搞定
  • 截止日期:旗标的事情如果搞定了,就先做紧急的事,做完之后压力会小很多
  • 上下文:如果这中间我需要外出或者打电话,这就算触发了情境,那么就打开上下文,批次处理
  • 项目:最后,打开项目推进一些重要不紧急的事情

屏幕快照 2013 11 15 下午2 49 44 7

现在仍然没有变过:)

这里是如何操作的视频:

结语:

每个人都和小沈一样,从职场新人过来的,但最后的发展却各不相同,从我自己的个人经验来说,最有体会的成长方法是“划小圈”,这个概念来自于乔希·维茨金的《学习之道》:

我 武术成长的下一个阶段就是把大的东西分解成各个细节的东西。鉴于我在象棋学习中对“用数字摆脱数字”方法的理解,我认为这个过程就是要挖掘技能的实质所 在,然后有效地压缩技能的外在表现同时又紧紧围绕技能的内在实质。一段时间之后,广度就会慢慢缩小而力量则会逐渐增加。我把这种方法叫做“划小圈”                            —乔希·维茨金

我的理解是:

1、找到关键的“小事”。对于得分后卫来说,关键小事是远距离投篮,对于中锋来说,关键小事是背身单打,对于销售员来说,关键小事是沟通,作为一个职场人,你的关键小事是什么?

2、用正确的方法去做“小事”。比如篮球运动员用正确的方式练习投篮,即使刚刚进入职场我们也要用正确的方法来面对琐碎的事情,做事靠系统,这是基本功。

3、精深练习。把一招练到极致就是绝招。

共勉!

PS:近期小强在多贝网上有微课程-《如何使用时间日志》,欢迎关注:http://www.duobei.com/course/3472034786

[转载]Android在AsyncHttpClient框架的基础上定制能直接返回对象数组的框架 - JamesXu的专栏 - 博客频道 - CSDN.NET

mikel阅读(893)

[转载]Android在AsyncHttpClient框架的基础上定制能直接返回对象数组的框架 – JamesXu的专栏 – 博客频道 – CSDN.NET.

以前听一个大神说过一句借用不知道哪位伟人的话:一个好的方法只有两三行,每个方法只专注一件事

本 着这样的精神,我们在拉取网路数据的时候希望能将拉取和解析分开,然而我们也希望有这样一个方法,传入UIL和表单数据后,能直接返回解析好的对象数组。 所以就有了这样的一个在AsyncHttpClient框架基础上定制的框架。由我的小伙伴lwz大神,和打下手的MoblieXu完成。

 

项目中用的服务器地址,为自己的小项目中一处拉取JSon,请各位手下留情。


首先看一下,我们希望的结果一个方法传入参数就能返回解析好的对象数组:

public class MainActivity extends Activity {
	private TextView tv_showData;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		tv_showData =(TextView) findViewById(R.id.tv_showData);
		getWebData();
	}

	private void getWebData() {
		GetJournalRequest getJournalRequest = new GetJournalRequest(
				getApplicationContext());
		getJournalRequest.request(new ResponseParseHandler<List<Journal>>() {
					@Override
					public void onStart() {
						//mProgressBar.setVisibility(View.VISIBLE);
					}

					@Override
					public void onSuccess(List<Journal> result) {
//						
					}

					@Override
					public void onFailure(String error, String message) {
						AppContext.showToast(message + "--" + error);
					}
				});
		
	}
}

直接就能从onSuccess回调中获取数据,保证了方法的简洁;

GetJournalRequest.java  
public class GetJournalRequest extends
		AbsAsyncHttpRequest<JSONArray, List<Journal>> {

	public static final String KEY_PARAM_JOURNALS = "Journals";

	public GetJournalRequest(Context context) {
		super(context);
	}

	@Override
	public String getAPI() {
		return API.GETJOURNAL_URL;
	}

	@Override
	public HttpMethod getHttpMethod() {
		return HttpMethod.GET;
	}

	@Override
	protected List<Journal> parseEntity(String parseData) throws JSONException {
		List<Journal> data = new ArrayList<Journal>();
		JSONObject jsonObject = new JSONObject(parseData);
		JSONArray jsonArray = jsonObject.getJSONArray(KEY_PARAM_JOURNALS);
		GetJournalParser getJournalParser = new GetJournalParser();
		for (int i = 0; i < jsonArray.length(); i++) {
			data.add(getJournalParser.parse(jsonArray.getJSONObject(i)));
		}
		return CollectionsUtil.isNullOrEmpty(data) ? new ArrayList<Journal>()
				: data;
	}
}

将指向的UIL 和传递方式,还有解析Json都定义在这个类中,这个类继承之AbsAsyncHttpRequest>

继承的时候设定了要返回的对象数组,

public abstract class AbsAsyncHttpRequest implements IAsyncHttpRequest 因为都是用泛型所以可以完成上面的设定。

public abstract class AbsAsyncHttpRequest<E, T> implements IAsyncHttpRequest<T> {

	public static final String JSON = "json";
	private Context mContext;
	private HttpRequestParams mParams;
	private ResponseParseHandler<T> mHandler;
	private String mPrefName;
	private String mCacheKey;

	public AbsAsyncHttpRequest(Context context) {
		mContext = context;
	}

	public void setParams(HttpRequestParams params) {
		mParams = params;
	}

	public abstract String getAPI();

	public abstract HttpMethod getHttpMethod();

	public void request(final ResponseParseHandler<T> handler) {
		request(null, null, handler);
	}

	@Override
	public void request(String prefName, String cacheKey,
			final ResponseParseHandler<T> handler) {
		this.mPrefName = prefName;
		this.mCacheKey = cacheKey;
		mHandler = handler;
		mResponse.updatePreNames(mContext,mPrefName, mCacheKey);
		if (getHttpMethod() == HttpMethod.GET) {
			doGet();
		} else {
			doPost();
		}
	}

	private void doGet() {
		if (isParamsEmpty(mParams)) {
			RequestClient.get(getAPI(), mResponse);
		} else {
			RequestClient.get(getUrlWithParams(getAPI(), mParams), mResponse);
		}
	}

	private void doPost() {
		if (isParamsEmpty(mParams)) {
			RequestClient.post(getAPI(), mResponse);
		} else {
			RequestClient.post(getAPI(), mParams.toString(), mResponse);
		}
	}

	private boolean isParamsEmpty(HttpRequestParams params) {
		return params == null || params.isEmpty();
	}

	private String getUrlWithParams(String url, HttpRequestParams params) {
		Set<String> keySet = params.keySet();
		StringBuffer sb = new StringBuffer();
		sb.append("?");
		int i=0;
		for (String key : keySet) {
			i++;
			if(i==keySet.size()){
				sb.append(key + "=" + params.get(key));
			}else{
				sb.append(key + "=" + params.get(key)+"&");
			}
		}
		AppContext.showLog("GET方式URL" + url + sb.toString());
		return url + sb.toString();
	}

	private AsyncHttpResponse mResponse = new AsyncHttpResponse(mContext,
			mPrefName, mCacheKey, new LoadDataHandler() {

				@Override
				public void onStart() {
//					AppContext.showLog("onRequestStart");
					mHandler.onStart();
				}

				@Override
				public void onLoadCaches(String data) {
//					AppContext.showLog("onLoadCaches");
					mHandler.onLoadCaches(parse(data));
				}

				@Override
				public void onSuccess(String data) {
//					AppContext.showLog("成功获得Json数据" + data);
					mHandler.onSuccess(parse(data));
				}

				@Override
				public void onFinished() {
//					AppContext.showLog("onFinished");
					mHandler.onFinished();
				}

				@Override
				public void onFailure(String error, String message) {
					AppContext.showLog(error + "   " + message);
					mHandler.onFailure(error, message);
				}

			});

	private T parse(String responseData) {
		JSONObject jsonData;
		try {
			jsonData = new JSONObject(responseData);
			BaseResponseParser<String> brp = new BaseResponseParser<String>(
					jsonData);
			if (brp.isSucceed()) {
				return parseEntity(brp.getData());
			} else {
				mHandler.onFailure(brp.getErrorCode(), brp.getErrorMessage());
			}
		} catch (JSONException e) {
			mHandler.onFailure("-1", e.getMessage());
		}
		return null;
	}

	protected abstract T parseEntity(String parseData) throws JSONException;
}

这里就是继承之AsyncHttpClient的基础类IAsyncHttpRequest,关键的是获取到数据之后将数据返回给了借口方法:

mHandler.onSuccess(parse(data));

private T parse(String responseData) {
		JSONObject jsonData;
		try {
			jsonData = new JSONObject(responseData);
			BaseResponseParser<String> brp = new BaseResponseParser<String>(
					jsonData);
			if (brp.isSucceed()) {
				return parseEntity(brp.getData());
			} else {
				mHandler.onFailure(brp.getErrorCode(), brp.getErrorMessage());
			}
		} catch (JSONException e) {
			mHandler.onFailure("-1", e.getMessage());
		}
		return null;
	}

而parse方法,完成了json最基础的解析,比如说判断ret标识,如果数据没有错误的话,那么执行parseEntity

protected abstract T parseEntity(String parseData) throws JSONException;

这是一个抽象方法,具体的实现是在继承它的子类里面实现,所以这样也就实现了将解析和拉取分开,要解析成什么对象,由继承它的类决定,这个parseEntity就像一个钩子,最后又把解析好的对象数组返回回来,交给接口mHandler.onSuccess(parse(data));最有趣的是这个接口的实现也就是我们最外面调用的

	@Override
public void onSuccess(List<Journal> result) {
//
}

方法中省略了很多细节,仅仅是简单分析,有兴趣的小伙伴可以到github上拉下来,自己研究一下,有改进的地方,一定提出了。

github:https://github.com/xujinyang/AsyncHttpClientByLX

[转载]android中的MVC-Android开发问题解答-eoe Android开发者社区_Android开发论坛

mikel阅读(1060)

[转载]android中的MVC-Android开发问题解答-eoe Android开发者社区_Android开发论坛.

Android比较完全的实现了MVC模式:
控制层:activity
视图层:View
业务层:自定义

简单的看,activity可以认为是MVC中的control,用于产生控制逻辑;View则是MVC中的view,用于展示软件界面。而view可以用xml来简单地表示和生成,美工可以采用一些界面设计器来设计界面,而不用理会复杂的java代码。这样Android就实现了逻辑与界面的良好分离,软件开发起来简单高效。
怎么将activity与view关联起来呢?
Activity提供了setContentView这个方法,只要继承Activity的子类调用该方法即可呈现view所描述的界面。View既可用xml描述,也可以用java代码来生成。

model层可以认为是values目录下的xml,也可以是java逻辑代码,为什么这么说呢?因为xml是可以用来存放数据的(比如样式、数组等),和javabean有着类似的功能。

附加别人的观点1:
android应用开发一 般来说由四大块构成 activity, intent, provider, broadcastreciver.从这种结构上来看,android系统是提供了从显示层到数据层到消息机制的一整套的应用开发方案,而且是一种比较先 进的解决方案。从写android代码的过程中,android项目整体是一种典型的MVC结构,非常类似于主要用于WEB开发的J2EE架构。xml布 局文件是view相当于JSP页面; activity和intent起到了controller的作用; provider对数据层做了良好的封装,而且provider把数据管理的范畴从数据库泛化到了数据的概念,不光管理数据记录,只要是数据文件(图片、 视频、声音文件、所有其他的一切的file)都纳入管理,且提供了数据共享的机制,这是比较出彩的地方; broadcastreceiver提供了一种良好的消息机制,使得一个应用不再是一个信息孤岛,而是和其他的应用、服务等构成了信息网络,从而极大的丰 富了应用的开发空间,给了应用开发者极大的想象创造的可能。

附加别人的观点2:
xml布局负责将界面布局做好,并且尽量做到合理分割与减少层次
Activity做好控件事件绑定与业务流程控制
Intent做好Activity间的session传递管理
自己创建Model(可以通过Observer模式进行绑定处理、并且包装好各种provider)将处理数据的工作做好。不建议简单地将各个数据字段散 乱地存放在Activity周围,而是借助数据Bean的思路存放在Model下面,这样在Model数据项变得庞大后难于管理与重构,而且这多为非面对 对象的设计方案。
Adapter是数据与呈现的粘合剂

[转载]从MVC框架看MVC架构的设计 - Laurence的技术博客 - 博客频道 - CSDN.NET

mikel阅读(1046)

[转载]从MVC框架看MVC架构的设计 – Laurence的技术博客 – 博客频道 – CSDN.NET.

尽管MVC早已不是什么新鲜话题了,但是从近些年一些优秀MVC框架的设计上,我们还是会发现MVC在架构设计上的一些新亮点。本文将对传统MVC架构中的一些弊病进行解读,了解一些优秀MVC框架是如何化解这些问题的,揭示其中所折射出的设计思想与设计理念。

 

MVC回顾

 

作 为一种经典到不能再经典的架构模式,MVC的成功有其必然的道理,这个道理不同的人会有不同的解读,笔者最认同的一种观点是:通过把职责、性质相近的成分 归结在一起,不相近的进行隔离,MVC将系统分解为模型、视图、控制器三部分,每一部分都相对独立,职责单一,在实现过程中可以专注于自身的核心逻辑。 MVC是对系统复杂性的一种合理的梳理与切分,它的思想实质就是“关注点分离”。至于MVC三元素的职责划分与相互关系,这里不再赘述,下图给出了非常细 致的说明。

 

图1:MVC组件的功能和关系[i]

 

View与Controller的解耦:mediator+二次事件委派

 

笔 者早年开发基于swing的GUI应用时,在架构MVC的实践过程中深刻体会到了view与controller之间的紧密耦合问题。在很多事件驱动的 GUI框架里,如swing,用户对view的任何操作都会触发一个事件,然后在listener的响应方法里进行处理。如果让view自己注册成为事件 的listener,则必须要在view中加入对controller的引用,这不是MVC希望看到的,因为这样view和controller就形成了 紧密的耦合关系。若将controller注册为listener,则事件响应将由controller承担,这又会导致controller处理其不该 涉及的展现逻辑,造成view和controller难以解耦的原因在于:多数的用户请求都包含一定成分的展现逻辑和一定成分的业务逻辑,两种逻辑揉合在 一个请求里,在处理的时候,view与controller很难合理地分工。解决这一问题的关键是要在view与controller之间建立一种可以将 展现逻辑与业务逻辑进行有效分割的机制,在这方面,PureMVC[ii]的设计非常值得参考,它通过引入mediator+二次事件委派机制很好的解决了view与controller之间的紧耦合问题。

 

Mediator 是一种设计模式,这种模式在组件化的图形界面框架中似乎有着普遍的应用场景,即使是在四人帮的《设计模式》一书中,也使用了一个图形界面程序的示例来讲解 mediator。mediator的设计用意在于通过一个媒介对象,完成一组对象的交互,避免对象间相互引用,产生复杂的依赖关系。mediator应 用于图形界面程序时,往往作为一组关系紧密的图形组件的交互媒介,完成组件间的协调工作(比如点选某一按钮,其他组件将不可用)。在PureMVC 中,mediator被广泛应用,其定位也发生了微妙的变化,它不再只是图形组件间的媒介,同时也成为了图形组件与command之间的媒介,这使得它不 再是可选的,而是成为了架构中的必需设施。对应到传统MVC架构中,mediator就是view与controller之间的媒介(当然,也依然是 view之间的媒介),所有从view发出的用户请求都经过了mediator再传递给controller,它的出现在一定程度上缓解了view与 controller的紧密耦合问题。

 

当view、mediator和controller三者被定义出来,并进行 了清晰的职责划分后,剩下的问题就是如何将它们串联起来,以完成一个用户请求了,在这方面,事件机制起到了至关重要的作用。事件机制可以让当前对象专注于 处理其职责范围内的事务,而不必关心超出部分由谁来处理以及怎样处理,当前对象只需要广播一个事件,就会有对此事件感兴趣的其他对象出来接手下一步的工 作,当前对象与接手对象之间不存在直接依赖,甚至感知不到彼此的存在,这是事件机制被普遍认为是一种松耦合机制的重要原因。讲到这里插一句题外话,在领域 驱动设计(Domain-Driven Design)里,著名的Domain Event模式也是有赖于事件机制的这一特性被创造出来的,其用意正是为了保证领域模型的纯净,避免领域模型对repository和service的直 接依赖。回到PureMVC, 我们来看在处理用户请求的过程中,事件机制是如何串联view、mediator和controller的。在PureMVC里,当一个用户请求下达时, 图形组件先在自身的事件响应方法中实现与自身相关的展现逻辑,然后收集数据,将数据置入一个新的event中,将其广播出去,这是第一次事件委派。这个 event会被一个mediator监听到,如果处理该请求需要其他图形组件的协助,mediator会协调它们处理应由它们承担的展现逻辑,然后 mediator再次发送一个event(这次的event在PureMVC里称之为notification),这个event会促使某个 command执行,完成业务逻辑的计算,这是第二次事件委派。在两次事件委派中,第一次事件委派让当事图形组件完成“处理其职责范围内的展现逻辑”后, 得以轻松“脱身”,免于被“协调其他图件处理剩余展现逻辑”和“选择并委派业务对象处理业务逻辑”所拖累。而“协调其他图形组件处理剩余展现逻辑”显然是 mediator的职责,于是第一次广播的事件被委派给了mediator. mediator在完成图形组件的协调工作后,并不会插手“选择并委派业务对象处理业务逻辑”的工作,这不是它的职责,因此,第二次事件委派发生了,一个 新的event由mediator广播出去,后被某个command响应到,由command完成了最后的工作——“选择并委派业务对象处理业务逻辑”。

图2:mediator+二次事件委派机制

总 结起来,PureMVC是通过在view与controller之间引入mediator,让view与controller变成间接依赖,用户请求从 view到mediator,再从mediator到controller均以事件方式委派,mediator+二次事件委派的组合可以说是一种“强力” 的解耦机制,它实现了view与controller之间的完全解耦。

 

从Controller到Command,自然粒度的回归

 

目 前,很多平台的主流MVC框架在设计上都引入了command模式,command模式的引入改变了传统MVC框架的结构,受冲击最大的就是 controller。在过去传统的MVC架构里,一个controller可能有多个方法,每个方法往往对应一个user action,因此,一个 controller往往对应多个user action,而在基于command的MVC架构里,一个command往往只对应一个user action。传统MVC架构里将一个user action委派到某个controller的某个方法的过程,在基于command的MVC架构里变成了将useraction与command一一绑 定的过程。如果说传统controller的管理方式是在user action与model之间建立“集中式”的映射,那么基于command的管理方式就是在user action与model之间建立“点对点式”的直连映射。

图3:从基于Controller到基于Command的架构演进

主 流MVC框架向command转型是有原因的,除了command自身的优势之外,一个非常重要的原因就是:由于缺少合理的组织依 据,controller的粒度很难拿捏。controller不同于view与model,view与model都有各自天然的粒度组织依据,view 的组织粒度直接承袭用户界面设计,model的组织粒度则是依据某种分析设计思想(如OOA/D)进行领域建模的结果,controller需要同时协调 view与model,但是view与model的组织结构和粒度都是不对等的,这就使得controller面临一个“在多大视图范围内沟通与协调多少 领域对象”的问题,由于找不出合理的组织依据,设计者在设计controller时往往感到无所适从。相比之下,command则完全没有 controller的困惑,因为command有一个天然的组织依据,这就是user action。针对一个user action设计一个command,然后将两者映射在一起,是一件非常自然而简单的事情。不过,需要说明的是这并不意味着所有command的粒度是一 样的,因为不同的user action所代表的业务量是不同的,因此也决定了command是有“大”有“小”的。遵循良好的设计原则,对某些较“大”的command进行分解, 从中抽离出一些可复用的部分封装成一些较“小”的command是值得推荐的。很多MVC框架就定义了一些相关的接口和抽象类用于支持基于组合模式的命令 拼装。

 

不管是基于controller还是基于command,MVC架构中界定的“协调view与model交 互”的控制器职责是不会变的,都需要相应的组件和机制去承载与实现。在基于command的架构里,command承担了过去controller的部分 职责,从某种意义上说 command是一种细粒度的controller,但是command的特性是偏“被动”的。一方面,它对于view和model的控制力比 controller弱化了很多, 比如,一般情况下command是不会直接操纵view的。另一方面,它不知道自己与什么样的user action映射在了一起,也不知道自己会在何种情况下被触发执行。支撑command的运行需要额外的注册、绑定和触发机制,是这些机制加上 command一起实现了controller的职责。由于现在多数基于command的MVC框架都实现并封装了这些重要的机制,所以从某种意义上说, 是这些框架自身扮演了controller角色。

 

小结

 

本文主要分析了过去传统MVC架构中存在的两大弊病:view与controller的紧密耦合以及controller粒度难以把控的问题,介绍了一些MVC框架是如何应对这些问题的,这些设计方案所体现出的优秀设计思想是非常值得学习的。

 


[ii] PureMVC是一种MVC框架,最初使用ActionScript 3实现,现在在多种语言平台上有实现版本。官方站点:http://puremvc.org/

 

更多精彩博文:

 

领域驱动设计(Domain Driven Design)参考架构详解

关于垂直切分Vertical Sharding的粒度

企业应用集成与开源ESB产品ServiceMix和Mule介绍

论基于数据访问的集合类(Data Access Based Collection)和领域事件(Domain Event)模式

关于系统异常设计的再思考

[转载]Android多线程Handler+ExecutorService(线程池)+MessageQueue模式+缓存模式-Android开发实例教程-eoe Android开发者社区_Android开发论坛

mikel阅读(852)

[转载]Handler+ExecutorService(线程池)+MessageQueue模式+缓存模式-Android开发实例教程-eoe Android开发者社区_Android开发论坛.

Handler+Runnable模式

我们先看一个并不是异步线程加载的例子,使用 Handler+Runnable模式。

这里为何不是新开线程的原因请参看这篇文章:Android Runnable 运行在那个线程 这里的代码其实是在UI 主线程中下载图片的,而不是新开线程。

我们运行下面代码时,会发现他其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面。

package ghj1976.AndroidTest;

import java.io.IOException;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;

public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
loadImage(<img class="\" id="\" onclick="\" onmouseover="\" alt="\" border="\" />",
R.id.imageView2);
loadImage("http://cache.soso.com/30d/img/web/logo.gif, R.id.imageView3);
loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif",
R.id.imageView4);
loadImage("http://images.cnblogs.com/logo_small.gif",
R.id.imageView5);
}

private Handler handler = new Handler();

private void loadImage(final String url, final int id) {
handler.post(new Runnable() {
public void run() {
Drawable drawable = null;
try {
drawable = Drawable.createFromStream(
new URL(url).openStream(), "image.gif");
} catch (IOException e) {
Log.d("test", e.getMessage());
}
if (drawable == null) {
Log.d("test", "null drawable");
} else {
Log.d("test", "not null drawable");
}
// 为了测试缓存而模拟的网络延时
SystemClock.sleep(2000);
((ImageView) MainActivity.this.findViewById(id))
.setImageDrawable(drawable);
}
});
}
}

Handler+Thread+Message模式

这种模式使用了线程,所以可以看到异步加载的效果。

核心代码:

package ghj1976.AndroidTest;

import java.io.IOException;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;

public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif",
R.id.imageView2);
loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
loadImage2("http://csdnimg.cn/www/images/csdnindex_logo.gif",
R.id.imageView4);
loadImage2("http://images.cnblogs.com/logo_small.gif",
R.id.imageView5);
}

final Handler handler2 = new Handler() {
@Override
public void handleMessage(Message msg) {
((ImageView) MainActivity.this.findViewById(msg.arg1))
.setImageDrawable((Drawable) msg.obj);
}
};

// 采用handler+Thread模式实现多线程异步加载
private void loadImage2(final String url, final int id) {
Thread thread = new Thread() {
@Override
public void run() {
Drawable drawable = null;
try {
drawable = Drawable.createFromStream(
new URL(url).openStream(), "image.png");
} catch (IOException e) {
Log.d("test", e.getMessage());
}

// 模拟网络延时
SystemClock.sleep(2000);

Message message = handler2.obtainMessage();
message.arg1 = id;
message.obj = drawable;
handler2.sendMessage(message);
}
};
thread.start();
thread = null;
}

}

这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去

Handler+ExecutorService(线程池)+MessageQueue模式

能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。

这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就来用它。

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

线程池的信息可以参看这篇文章:Java&Android的线程池-ExecutorService 下面的演示例子是创建一个可重用固定线程数的线程池

package ghj1976.AndroidTest;

import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;

public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif",
R.id.imageView2);
loadImage3("http://cache.soso.com/30d/img/web/logo.gif",
R.id.imageView3);
loadImage3("http://csdnimg.cn/www/images/csdnindex_logo.gif",
R.id.imageView4);
loadImage3("http://images.cnblogs.com/logo_small.gif",
R.id.imageView5);
}

private Handler handler = new Handler();

private ExecutorService executorService = Executors.newFixedThreadPool(5);

// 引入线程池来管理多线程
private void loadImage3(final String url, final int id) {
executorService.submit(new Runnable() {
public void run() {
try {
final Drawable drawable = Drawable.createFromStream(
new URL(url).openStream(), "image.png");
// 模拟网络延时
SystemClock.sleep(2000);
handler.post(new Runnable() {
public void run() {
((ImageView) MainActivity.this.findViewById(id))
.setImageDrawable(drawable);
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
}

这里我们象第一步一样使用了 handler.post(new Runnable() {  更新前段显示当然是在UI主线程,我们还有 executorService.submit(new Runnable() { 来确保下载是在线程池的线程中。

Handler+ExecutorService(线程池)+MessageQueue+缓存模式

下面比起前一个做了几个改造:

  • 把整个代码封装在一个类中
  • 为了避免出现同时多次下载同一幅图的问题,使用了本地缓存

封装的类:

package ghj1976.AndroidTest;
 
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.SystemClock;
 
public class AsyncImageLoader3 {
        // 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
        public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
         
        private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五个线程来执行任务
        private final Handler handler = new Handler();
 
        /**
         * 
         * @param imageUrl
         *            图像url地址
         * @param callback
         *            回调接口
         * <a href="\"http://www.eoeandroid.com/home.php?mod=space&uid=7300\"" target="\"_blank\"">@return</a> 返回内存中缓存的图像,第一次加载返回null
         */
        public Drawable loadDrawable(final String imageUrl,
                        final ImageCallback callback) {
                // 如果缓存过就从缓存中取出数据
                if (imageCache.containsKey(imageUrl)) {
                        SoftReference<Drawable> softReference = imageCache.get(imageUrl);
                        if (softReference.get() != null) {
                                return softReference.get();
                        }
                }
                // 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
                executorService.submit(new Runnable() {
                        public void run() {
                                try {
                                        final Drawable drawable = loadImageFromUrl(imageUrl); 
                                                 
                                        imageCache.put(imageUrl, new SoftReference<Drawable>(
                                                        drawable));
 
                                        handler.post(new Runnable() {
                                                public void run() {
                                                        callback.imageLoaded(drawable);
                                                }
                                        });
                                } catch (Exception e) {
                                        throw new RuntimeException(e);
                                }
                        }
                });
                return null;
        }
 
        // 从网络上取数据方法
        protected Drawable loadImageFromUrl(String imageUrl) {
                try {
                        // 测试时,模拟网络延时,实际时这行代码不能有
                        SystemClock.sleep(2000);
 
                        return Drawable.createFromStream(new URL(imageUrl).openStream(),
                                        "image.png");
 
                } catch (Exception e) {
                        throw new RuntimeException(e);
                }
        }
 
        // 对外界开放的回调接口
        public interface ImageCallback {
                // 注意 此方法是用来设置目标对象的图像资源
                public void imageLoaded(Drawable imageDrawable);
        }
}

说明:

final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。参看:Java关键字final、static使用总结
这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的,更详细的可以参看:内存优化的两个类:SoftReference 和 WeakReference

前段调用:

package ghj1976.AndroidTest;
 
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
 
import android.widget.ImageView;
 
public class MainActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
                loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
                loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif",
                                R.id.imageView2);
                loadImage4("http://cache.soso.com/30d/img/web/logo.gif",
                                R.id.imageView3);
                loadImage4("http://csdnimg.cn/www/images/csdnindex_logo.gif",
                                R.id.imageView4);
                loadImage4("http://images.cnblogs.com/logo_small.gif",
                                R.id.imageView5);
        }
 
        private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();
 
        // 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
        private void loadImage4(final String url, final int id) {
                // 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
                Drawable cacheImage = asyncImageLoader3.loadDrawable(url,
                                new AsyncImageLoader3.ImageCallback() {
                                        // 请参见实现:如果第一次加载url时下面方法会执行
                                        public void imageLoaded(Drawable imageDrawable) {
                                                ((ImageView) findViewById(id))
                                                                .setImageDrawable(imageDrawable);
                                        }
                                });
                if (cacheImage != null) {
                        ((ImageView) findViewById(id)).setImageDrawable(cacheImage);
                }
        }
 
}

[转载]cocos2d-x 手游研发小技巧(3)Android界面分辨率适配方案 - zisouTags - 博客园

mikel阅读(923)

[转载]【cocos2d-x 手游研发小技巧(3)Android界面分辨率适配方案】 – zisouTags – 博客园.

先感叹一下吧~~Android的各种分辨率各种适配虐我千百遍,每次新项目我依旧待它如初恋····

每家公司都有自己项目工程适配的方案,这种东西就是没有最好,只有最适合!!!

这次新项目专项针对Android,目的性强,适配方案我觉得2套图去兼容android各种分辨率;

我们先了解一下android手机上的屏幕密度:

Android主要有以下几种屏:

QVGA和WQVGA屏density=120;

HVGA屏density=160;

WVGA屏density=240….更多 density值表示每英寸有多少个显示点;

和分辨率不一样,大部分做应用的就可以通过屏幕密度走,那么游戏中也可以类似走这种路线;

但是现在的出现了超高清屏幕,诸如小米,三星稍微比较高端一点新出的机型分辨率都非常之高达到FHD;

FHD级别就是我们所谓的屏幕像素达到了1920*1080P格式,也就是全高清屏幕的简称了,要适应这种屏幕得单独适配;

废话不多了就直接上解决方案吧:

思路1:背景适配,然后往背景里面add部分UI原件的方法。先解决背景适配;
思路2:根据屏幕尺寸,去适应“屏幕窗口UI原件”,不属于任何原件直接Add到CClayer中的;

代码如下:

先建立一个VisibleRect类去通过CCEGLView拿到屏幕尺寸,然后再取八个点作为静态方法以后直接当做目标定位使用

#ifndef __VISIBLERECT_H__
#define __VISIBLERECT_H__

#include "cocos2d.h"
USING_NS_CC;

class VisibleRect
{
public:
static CCRect getVisibleRect();

static CCPoint left();
static CCPoint right();
static CCPoint top();
static CCPoint bottom();
static CCPoint center();
static CCPoint leftTop();
static CCPoint rightTop();
static CCPoint leftBottom();
static CCPoint rightBottom();
private:
static void lazyInit();
static CCRect s_visibleRect;
};

#endif /* __VISIBLERECT_H__ */

VisibleRect.cpp

#include "VisibleRect.h"

CCRect VisibleRect::s_visibleRect;

void VisibleRect::lazyInit()
{
if (s_visibleRect.size.width == 0.0f &amp;&amp; s_visibleRect.size.height == 0.0f)
{
CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();
s_visibleRect.origin = pEGLView-&gt;getVisibleOrigin();
s_visibleRect.size = pEGLView-&gt;getVisibleSize();
}
}

CCRect VisibleRect::getVisibleRect()
{
lazyInit();
return CCRectMake(s_visibleRect.origin.x, s_visibleRect.origin.y, s_visibleRect.size.width, s_visibleRect.size.height);
}

CCPoint VisibleRect::left()
{
lazyInit();
return ccp(s_visibleRect.origin.x, s_visibleRect.origin.y+s_visibleRect.size.height/2);
}

CCPoint VisibleRect::right()
{
lazyInit();
return ccp(s_visibleRect.origin.x+s_visibleRect.size.width, s_visibleRect.origin.y+s_visibleRect.size.height/2);
}

CCPoint VisibleRect::top()
{
lazyInit();
return ccp(s_visibleRect.origin.x+s_visibleRect.size.width/2, s_visibleRect.origin.y+s_visibleRect.size.height);
}

CCPoint VisibleRect::bottom()
{
lazyInit();
return ccp(s_visibleRect.origin.x+s_visibleRect.size.width/2, s_visibleRect.origin.y);
}

CCPoint VisibleRect::center()
{
lazyInit();
return ccp(s_visibleRect.origin.x+s_visibleRect.size.width/2, s_visibleRect.origin.y+s_visibleRect.size.height/2);
}

CCPoint VisibleRect::leftTop()
{
lazyInit();
return ccp(s_visibleRect.origin.x, s_visibleRect.origin.y+s_visibleRect.size.height);
}

CCPoint VisibleRect::rightTop()
{
lazyInit();
return ccp(s_visibleRect.origin.x+s_visibleRect.size.width, s_visibleRect.origin.y+s_visibleRect.size.height);
}

CCPoint VisibleRect::leftBottom()
{
lazyInit();
return s_visibleRect.origin;
}

CCPoint VisibleRect::rightBottom()
{
lazyInit();
return ccp(s_visibleRect.origin.x+s_visibleRect.size.width, s_visibleRect.origin.y);
}

有了这个工具类可以做很多事情了;

下面我们需要去适配背景,具体方法如下:

CCSprite* PublicShowUI::setTagScale(CCSprite* tagSprite)
{
float last_X,last_Y;
float X2 = tagSprite-&gt;getContentSize().width;
float Y2 = tagSprite-&gt;getContentSize().height;
last_X = ( (float)VisibleRect::getVisibleRect().size.width/X2) ;
last_Y = ( (float)VisibleRect::getVisibleRect().size.height/Y2);
tagSprite-&gt;setScaleX(last_X);
tagSprite-&gt;setScaleY(last_Y);
return tagSprite;

}

要适应各种大小分辨率,一套图是不够用的,根据自己项目的需要去制作2套图,我推荐的是如下分辨率套图:

800*480 一套

1136*640 二套

HD高清 第三套 1920 * 1080 目前手机上面比较高的,据说三星的超过2000,可以自己去设定!

有了这些图,分别整理自己的资源文件夹然后去适配,代码如下:

/******************
*获取屏幕分辨率
*根据分辨率计算使用哪一套资源
******************/
int PublicShowUI::getinch(void)
{
int lastinch = -1;
CCSize winSizeInPixels = screenSize;
if(winSizeInPixels.width&gt;=800&amp;&amp;winSizeInPixels.width&lt;=960) { lastinch = INCH_1;//ihpone3.5寸 } else if(winSizeInPixels.width&gt;960&amp;&amp;winSizeInPixels.width&lt;=1136) { lastinch = INCH_2;//ihpone4寸及大部分android4寸左右屏幕 } else if(winSizeInPixels.width&gt;1136&amp;&amp;winSizeInPixels.width&lt;=1920)
{
lastinch = INCH_MAX;//超高清屏幕
}
else
{
lastinch = INCH_2;
}
return lastinch;
}

/******************
*根据自定义图片路径去取不同套图的路径
*imgres 格式:imgdir%d/imgname.png
******************/
CCString* PublicShowUI::getResImgPath(char* imgres)
{
sprintf(str, imgres ,getinch());
return CCString::create(str);
}

获取屏幕分辨率screenSize

CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();
pDirector->setOpenGLView(pEGLView);
CCSize screenSize = pEGLView->getFrameSize();

 

使用:

view_Room = new View_Room(this, PublicShowUI::getResImgPath(IMG_ROOM_BACKGROUND), 1, VisibleRect::center());

背景图一定要居中显示:

VisibleRect::center()

OK以上便是我适配的方案和思路;

下面我上2张IOS模拟器上的图,我故意再背景图的边框加了绿线,来显示区别显示全屏,然后用了两套图去适配!

3.5寸IOS的分辨率如下:

 

4寸分辨率如下:

 

[转载]发一个高性能的敏感词过滤算法 可以忽略大小写、全半角、简繁体、特殊符号干扰 - passer.net - 博客园

mikel阅读(1028)

转载发一个高性能的敏感词过滤算法 可以忽略大小写、全半角、简繁体、特殊符号干扰 – passer.net – 博客园.

发一个高性能的敏感词过滤算法 可以忽略大小写、全半角、简繁体、特殊符号干扰

敏感词查找或者过滤是每个天朝互联网从业者都不能忽略的一件事情。

写之前已经参阅了博客园的大量敏感词的查找或者过滤算法,发现没用完全符合自己需求的算法,所以自己花时间做了一个

需求主要有三点:

1、高性能和可靠性,因为基于百万级PV全站的敏感词实时过滤,这个无疑是很致命的,可以接受的程度是每个页面100k字节 关键词库1000个左右 必须在毫秒级别完成替换。CPU和内存无明显增加。

2、最好能识别一定的干扰 如 “法-輪*功”  “F Uc          K ”。

3、可以支持自定义高亮算法,方便用户和编辑快速找到关键词

算法要点

1、构建一颗关键词树

/// <summary>
        /// 敏感词树
        /// </summary>
        private class FilterKeyWordsNode
        {
            public Dictionary<char, FilterKeyWordsNode> Child;
            public bool IsEnd;
        }

 2、关键词和目标字符串做同样的映射 把大小写 全半角 简繁体 甚至是异体字火星文统一映射到同一字符

/// <summary>
/// 字符预处理
/// </summary>
private static char[] Translation(String src)
{
    char[] c = src.ToCharArray();
    for (int i = 0; i < c.Length; i++)
    {
        /*全角=>半角*/
        if (c[i] > 0xFF00 && c[i] < 0xFF5F)
            c[i] = (char)(c[i] - 0xFEE0);
 
        /*大写=>小写*/
        if (c[i] > 0x40 && c[i] < 0x5b)
            c[i] = (char)(c[i] + 0x20);
 
        /*繁体=>简体*/
        if (c[i] > 0x4E00 && c[i] < 0x9FFF)
        {
            char chinese;
            if (TranslationChinese.TryGetValue(c[i], out chinese))
                c[i] = chinese;
        }
    }
    return c;
}

 3、快速跳过特殊符号

/// <summary>
        /// 跳过特殊符号 ASCII范围 排除 数字字母
        /// </summary>
        private static bool IsSkip(char firstChar)
        {
            if (firstChar < '0')
                return true;
            if (firstChar > '9' && firstChar < 'A')
                return true;
            if (firstChar > 'Z' && firstChar < 'a')
                return true;
            if (firstChar > 'z' && firstChar < 128)
                return true;
            return false;
        }

4、查找的时候返回关键词的偏移位置和长度便于替换

/// <summary>
        /// 位置查找
        /// </summary>
        private static Dictionary<int, int> Find(string src)
        {
            if (_root == null)
                throw new InvalidOperationException("未初始化");

            var findResult = new Dictionary<int, int>();
            if (string.IsNullOrEmpty(src))
                return findResult;


            var text = Translation(src);
            int start = 0;

            while (start < text.Length)
            {
                int length = 0;
                var firstChar = text[start + length];
                var node = _root;

                //跳过特殊符号
                while (IsSkip(firstChar) && (start + length + 1) < text.Length)
                {
                    start++;
                    firstChar = text[start + length];
                }

                //不匹配首字符 移动起始位置
                while (!node.Child.ContainsKey(firstChar) && start < text.Length - 1)
                {
                    start++;
                    firstChar = text[start + length];
                }

                //部分匹配 移动结束位置 直到不匹配
                while (node.Child != null && node.Child.ContainsKey(firstChar))
                {
                    node = node.Child[firstChar];
                    length++;

                    if((start + length) == text.Length)
                        break;

                    firstChar = text[start + length];

                    //跳过忽略词
                    while (IsSkip(firstChar) && !node.IsEnd && (start + length + 1) < text.Length)
                    {
                        length++;
                        firstChar = text[start + length];
                    }


                }

                //完整匹配 把起始位置移到结束位置
                if (node.IsEnd)
                {
                    findResult.Add(start, length);
                    start += length - 1;
                }

                start++;

            }

            return findResult;


        }

普通PC性能统计1000个关键词库

最差情况 全是关键字 每秒10MB/s
正常字符 含少量关键词 每秒30MB/s
最好情况 全是忽略词 每秒50MB/s

性能和文本长度呈线性增长关系 关键词数量对性能影响不明显。

源代码

/// <summary>
    /// 敏感词过滤 已忽略大小写 全半角 简繁体差异 排除特殊符号干扰 by http://passer.cnblogs.com
    /// </summary>
    public static class FilterKeyWords
    {


        private static readonly object LockObj = new object();
        private static FilterKeyWordsNode _root;
        private const string TraditionalChinese = "皚藹礙愛翺襖奧壩罷擺敗頒辦絆幫綁鎊謗剝飽寶報鮑輩貝鋇狽備憊繃筆畢斃閉邊編貶變辯辮鼈癟瀕濱賓擯餅撥缽鉑駁蔔補參蠶殘慚慘燦蒼艙倉滄廁側冊測層詫攙摻蟬饞讒纏鏟産闡顫場嘗長償腸廠暢鈔車徹塵陳襯撐稱懲誠騁癡遲馳恥齒熾沖蟲寵疇躊籌綢醜櫥廚鋤雛礎儲觸處傳瘡闖創錘純綽辭詞賜聰蔥囪從叢湊竄錯達帶貸擔單鄲撣膽憚誕彈當擋黨蕩檔搗島禱導盜燈鄧敵滌遞締點墊電澱釣調叠諜疊釘頂錠訂東動棟凍鬥犢獨讀賭鍍鍛斷緞兌隊對噸頓鈍奪鵝額訛惡餓兒爾餌貳發罰閥琺礬釩煩範販飯訪紡飛廢費紛墳奮憤糞豐楓鋒風瘋馮縫諷鳳膚輻撫輔賦複負訃婦縛該鈣蓋幹趕稈贛岡剛鋼綱崗臯鎬擱鴿閣鉻個給龔宮鞏貢鈎溝構購夠蠱顧剮關觀館慣貫廣規矽歸龜閨軌詭櫃貴劊輥滾鍋國過駭韓漢閡鶴賀橫轟鴻紅後壺護滬戶嘩華畫劃話懷壞歡環還緩換喚瘓煥渙黃謊揮輝毀賄穢會燴彙諱誨繪葷渾夥獲貨禍擊機積饑譏雞績緝極輯級擠幾薊劑濟計記際繼紀夾莢頰賈鉀價駕殲監堅箋間艱緘繭檢堿鹼揀撿簡儉減薦檻鑒踐賤見鍵艦劍餞漸濺澗漿蔣槳獎講醬膠澆驕嬌攪鉸矯僥腳餃繳絞轎較稭階節莖驚經頸靜鏡徑痙競淨糾廄舊駒舉據鋸懼劇鵑絹傑潔結誡屆緊錦僅謹進晉燼盡勁荊覺決訣絕鈞軍駿開凱顆殼課墾懇摳庫褲誇塊儈寬礦曠況虧巋窺饋潰擴闊蠟臘萊來賴藍欄攔籃闌蘭瀾讕攬覽懶纜爛濫撈勞澇樂鐳壘類淚籬離裏鯉禮麗厲勵礫曆瀝隸倆聯蓮連鐮憐漣簾斂臉鏈戀煉練糧涼兩輛諒療遼鐐獵臨鄰鱗凜賃齡鈴淩靈嶺領餾劉龍聾嚨籠壟攏隴樓婁摟簍蘆盧顱廬爐擄鹵虜魯賂祿錄陸驢呂鋁侶屢縷慮濾綠巒攣孿灤亂掄輪倫侖淪綸論蘿羅邏鑼籮騾駱絡媽瑪碼螞馬罵嗎買麥賣邁脈瞞饅蠻滿謾貓錨鉚貿麽黴沒鎂門悶們錳夢謎彌覓綿緬廟滅憫閩鳴銘謬謀畝鈉納難撓腦惱鬧餒膩攆撚釀鳥聶齧鑷鎳檸獰甯擰濘鈕紐膿濃農瘧諾歐鷗毆嘔漚盤龐賠噴鵬騙飄頻貧蘋憑評潑頗撲鋪樸譜臍齊騎豈啓氣棄訖牽扡釺鉛遷簽謙錢鉗潛淺譴塹槍嗆牆薔強搶鍬橋喬僑翹竅竊欽親輕氫傾頃請慶瓊窮趨區軀驅齲顴權勸卻鵲讓饒擾繞熱韌認紉榮絨軟銳閏潤灑薩鰓賽傘喪騷掃澀殺紗篩曬閃陝贍繕傷賞燒紹賒攝懾設紳審嬸腎滲聲繩勝聖師獅濕詩屍時蝕實識駛勢釋飾視試壽獸樞輸書贖屬術樹豎數帥雙誰稅順說碩爍絲飼聳慫頌訟誦擻蘇訴肅雖綏歲孫損筍縮瑣鎖獺撻擡攤貪癱灘壇譚談歎湯燙濤縧騰謄銻題體屜條貼鐵廳聽烴銅統頭圖塗團頹蛻脫鴕馱駝橢窪襪彎灣頑萬網韋違圍爲濰維葦偉僞緯謂衛溫聞紋穩問甕撾蝸渦窩嗚鎢烏誣無蕪吳塢霧務誤錫犧襲習銑戲細蝦轄峽俠狹廈鍁鮮纖鹹賢銜閑顯險現獻縣餡羨憲線廂鑲鄉詳響項蕭銷曉嘯蠍協挾攜脅諧寫瀉謝鋅釁興洶鏽繡虛噓須許緒續軒懸選癬絢學勳詢尋馴訓訊遜壓鴉鴨啞亞訝閹煙鹽嚴顔閻豔厭硯彥諺驗鴦楊揚瘍陽癢養樣瑤搖堯遙窯謠藥爺頁業葉醫銥頤遺儀彜蟻藝億憶義詣議誼譯異繹蔭陰銀飲櫻嬰鷹應纓瑩螢營熒蠅穎喲擁傭癰踴詠湧優憂郵鈾猶遊誘輿魚漁娛與嶼語籲禦獄譽預馭鴛淵轅園員圓緣遠願約躍鑰嶽粵悅閱雲鄖勻隕運蘊醞暈韻雜災載攢暫贊贓髒鑿棗竈責擇則澤賊贈紮劄軋鍘閘詐齋債氈盞斬輾嶄棧戰綻張漲帳賬脹趙蟄轍鍺這貞針偵診鎮陣掙睜猙幀鄭證織職執紙摯擲幟質鍾終種腫衆謅軸皺晝驟豬諸誅燭矚囑貯鑄築駐專磚轉賺樁莊裝妝壯狀錐贅墜綴諄濁茲資漬蹤綜總縱鄒詛組鑽緻鐘麼為隻兇準啟闆裡靂餘鍊洩";
        private const string SimplifiedChinese = "皑蔼碍爱翱袄奥坝罢摆败颁办绊帮绑镑谤剥饱宝报鲍辈贝钡狈备惫绷笔毕毙闭边编贬变辩辫鳖瘪濒滨宾摈饼拨钵铂驳卜补参蚕残惭惨灿苍舱仓沧厕侧册测层诧搀掺蝉馋谗缠铲产阐颤场尝长偿肠厂畅钞车彻尘陈衬撑称惩诚骋痴迟驰耻齿炽冲虫宠畴踌筹绸丑橱厨锄雏础储触处传疮闯创锤纯绰辞词赐聪葱囱从丛凑窜错达带贷担单郸掸胆惮诞弹当挡党荡档捣岛祷导盗灯邓敌涤递缔点垫电淀钓调迭谍叠钉顶锭订东动栋冻斗犊独读赌镀锻断缎兑队对吨顿钝夺鹅额讹恶饿儿尔饵贰发罚阀珐矾钒烦范贩饭访纺飞废费纷坟奋愤粪丰枫锋风疯冯缝讽凤肤辐抚辅赋复负讣妇缚该钙盖干赶秆赣冈刚钢纲岗皋镐搁鸽阁铬个给龚宫巩贡钩沟构购够蛊顾剐关观馆惯贯广规硅归龟闺轨诡柜贵刽辊滚锅国过骇韩汉阂鹤贺横轰鸿红后壶护沪户哗华画划话怀坏欢环还缓换唤痪焕涣黄谎挥辉毁贿秽会烩汇讳诲绘荤浑伙获货祸击机积饥讥鸡绩缉极辑级挤几蓟剂济计记际继纪夹荚颊贾钾价驾歼监坚笺间艰缄茧检碱硷拣捡简俭减荐槛鉴践贱见键舰剑饯渐溅涧浆蒋桨奖讲酱胶浇骄娇搅铰矫侥脚饺缴绞轿较秸阶节茎惊经颈静镜径痉竞净纠厩旧驹举据锯惧剧鹃绢杰洁结诫届紧锦仅谨进晋烬尽劲荆觉决诀绝钧军骏开凯颗壳课垦恳抠库裤夸块侩宽矿旷况亏岿窥馈溃扩阔蜡腊莱来赖蓝栏拦篮阑兰澜谰揽览懒缆烂滥捞劳涝乐镭垒类泪篱离里鲤礼丽厉励砾历沥隶俩联莲连镰怜涟帘敛脸链恋炼练粮凉两辆谅疗辽镣猎临邻鳞凛赁龄铃凌灵岭领馏刘龙聋咙笼垄拢陇楼娄搂篓芦卢颅庐炉掳卤虏鲁赂禄录陆驴吕铝侣屡缕虑滤绿峦挛孪滦乱抡轮伦仑沦纶论萝罗逻锣箩骡骆络妈玛码蚂马骂吗买麦卖迈脉瞒馒蛮满谩猫锚铆贸么霉没镁门闷们锰梦谜弥觅绵缅庙灭悯闽鸣铭谬谋亩钠纳难挠脑恼闹馁腻撵捻酿鸟聂啮镊镍柠狞宁拧泞钮纽脓浓农疟诺欧鸥殴呕沤盘庞赔喷鹏骗飘频贫苹凭评泼颇扑铺朴谱脐齐骑岂启气弃讫牵扦钎铅迁签谦钱钳潜浅谴堑枪呛墙蔷强抢锹桥乔侨翘窍窃钦亲轻氢倾顷请庆琼穷趋区躯驱龋颧权劝却鹊让饶扰绕热韧认纫荣绒软锐闰润洒萨鳃赛伞丧骚扫涩杀纱筛晒闪陕赡缮伤赏烧绍赊摄慑设绅审婶肾渗声绳胜圣师狮湿诗尸时蚀实识驶势释饰视试寿兽枢输书赎属术树竖数帅双谁税顺说硕烁丝饲耸怂颂讼诵擞苏诉肃虽绥岁孙损笋缩琐锁獭挞抬摊贪瘫滩坛谭谈叹汤烫涛绦腾誊锑题体屉条贴铁厅听烃铜统头图涂团颓蜕脱鸵驮驼椭洼袜弯湾顽万网韦违围为潍维苇伟伪纬谓卫温闻纹稳问瓮挝蜗涡窝呜钨乌诬无芜吴坞雾务误锡牺袭习铣戏细虾辖峡侠狭厦锨鲜纤咸贤衔闲显险现献县馅羡宪线厢镶乡详响项萧销晓啸蝎协挟携胁谐写泻谢锌衅兴汹锈绣虚嘘须许绪续轩悬选癣绚学勋询寻驯训讯逊压鸦鸭哑亚讶阉烟盐严颜阎艳厌砚彦谚验鸯杨扬疡阳痒养样瑶摇尧遥窑谣药爷页业叶医铱颐遗仪彝蚁艺亿忆义诣议谊译异绎荫阴银饮樱婴鹰应缨莹萤营荧蝇颖哟拥佣痈踊咏涌优忧邮铀犹游诱舆鱼渔娱与屿语吁御狱誉预驭鸳渊辕园员圆缘远愿约跃钥岳粤悦阅云郧匀陨运蕴酝晕韵杂灾载攒暂赞赃脏凿枣灶责择则泽贼赠扎札轧铡闸诈斋债毡盏斩辗崭栈战绽张涨帐账胀赵蛰辙锗这贞针侦诊镇阵挣睁狰帧郑证织职执纸挚掷帜质钟终种肿众诌轴皱昼骤猪诸诛烛瞩嘱贮铸筑驻专砖转赚桩庄装妆壮状锥赘坠缀谆浊兹资渍踪综总纵邹诅组钻致钟么为只凶准启板里雳余链泄";
        private static readonly Dictionary<char, char> TranslationChinese = TraditionalChinese.Select((c, i) => new { c, i }).ToDictionary(p => p.c, p => SimplifiedChinese[p.i]);

        /// <summary>
        /// 初始化 使用前必须调用一次
        /// </summary>
        /// <param name="keyWords">敏感词列表</param>
        public static void Init(string[] keyWords)
        {
            if (_root != null) return;
            lock (LockObj)
            {
                _root = new FilterKeyWordsNode();
                var list = keyWords.Select(p => new string(Translation(p))).Distinct().OrderBy(p => p).ThenBy(p => p.Length).ToList();
                for (int i = list.Min(p => p.Length); i <= list.Max(p => p.Length); i++)
                {
                    int i1 = i;
                    var startList = list.Where(p => p.Length >= i1).Select(p => p.Substring(0, i1)).Distinct();
                    foreach (var startWord in startList)
                    {
                        var tmp = _root;
                        for (int j = 0; j < startWord.Length; j++)
                        {
                            var t = startWord[j];
                            if (tmp.Child == null)
                                tmp.Child = new Dictionary<char, FilterKeyWordsNode>();

                            if (!tmp.Child.ContainsKey(t))
                            {
                                tmp.Child.Add(t, new FilterKeyWordsNode { IsEnd = list.Contains(startWord.Substring(0, 1 + j)) });
                            }

                            tmp = tmp.Child[t];
                        }
                    }
                }

            }
        }

        /// <summary>
        /// 查找含有的关键词
        /// </summary>
        public static bool Find(string text, out string[] keyWords)
        {

            keyWords = Find(text).Select(p => text.Substring(p.Key, p.Value)).Distinct().ToArray();
            return keyWords.Length > 0;
        }

        /// <summary>
        /// 简单快速替换
        /// </summary>
        public static string Replace(string text)
        {
            var dic = Find(text);
            var list = text.ToArray();
            foreach (var i in dic)
            {
                for (int j = i.Key; j < i.Key + i.Value; j++)
                {
                    list[j] = '*';
                }
            }
            return new string(list.ToArray());
        }

        /// <summary>
        /// 自定义过滤
        /// </summary>
        public static string Replace(string text, ReplaceDelegate replaceAction)
        {

            var dic = Find(text);
            var list = text.ToList();
            var offset = 0;
            foreach (var i in dic)
            {

                list.RemoveRange(i.Key + offset, i.Value);
                var newText = replaceAction(text.Substring(i.Key, i.Value), i.Key, i.Value);
                list.InsertRange(i.Key + offset, newText);

                offset = offset + newText.Length - i.Value;
            }
            return new string(list.ToArray());
        }


        /// <summary>
        /// 位置查找
        /// </summary>
        private static Dictionary<int, int> Find(string src)
        {
            if (_root == null)
                throw new InvalidOperationException("未初始化");

            var findResult = new Dictionary<int, int>();
            if (string.IsNullOrEmpty(src))
                return findResult;


            var text = Translation(src);
            int start = 0;

            while (start < text.Length)
            {
                int length = 0;
                var firstChar = text[start + length];
                var node = _root;

                //跳过特殊符号
                while (IsSkip(firstChar) && (start + length + 1) < text.Length)
                {
                    start++;
                    firstChar = text[start + length];
                }

                //不匹配首字符 移动起始位置
                while (!node.Child.ContainsKey(firstChar) && start < text.Length - 1)
                {
                    start++;
                    firstChar = text[start + length];
                }

                //部分匹配 移动结束位置 直到不匹配
                while (node.Child != null && node.Child.ContainsKey(firstChar))
                {
                    node = node.Child[firstChar];
                    length++;

                    if((start + length) == text.Length)
                        break;

                    firstChar = text[start + length];

                    //跳过忽略词
                    while (IsSkip(firstChar) && !node.IsEnd && (start + length + 1) < text.Length)
                    {
                        length++;
                        firstChar = text[start + length];
                    }


                }

                //完整匹配 把起始位置移到结束位置
                if (node.IsEnd)
                {
                    findResult.Add(start, length);
                    start += length - 1;
                }

                start++;

            }

            return findResult;


        }


        /// <summary>
        /// 字符预处理
        /// </summary>
        private static char[] Translation(String src)
        {
            char[] c = src.ToCharArray();
            for (int i = 0; i < c.Length; i++)
            {
                /*全角=>半角*/
                if (c[i] > 0xFF00 && c[i] < 0xFF5F)
                    c[i] = (char)(c[i] - 0xFEE0);

                /*大写=>小写*/
                if (c[i] > 0x40 && c[i] < 0x5b)
                    c[i] = (char)(c[i] + 0x20);

                /*繁体=>简体*/
                if (c[i] > 0x4E00 && c[i] < 0x9FFF)
                {
                    char chinese;
                    if (TranslationChinese.TryGetValue(c[i], out chinese))
                        c[i] = chinese;
                }
            }
            return c;
        }



        /// <summary>
        /// 跳过特殊符号 ASCII范围 排除 数字字母
        /// </summary>
        private static bool IsSkip(char firstChar)
        {
            if (firstChar < '0')
                return true;
            if (firstChar > '9' && firstChar < 'A')
                return true;
            if (firstChar > 'Z' && firstChar < 'a')
                return true;
            if (firstChar > 'z' && firstChar < 128)
                return true;
            return false;
        }


        /// <summary>
        /// 敏感词树
        /// </summary>
        private class FilterKeyWordsNode
        {
            public Dictionary<char, FilterKeyWordsNode> Child;
            public bool IsEnd;
        }

        /// <summary>
        /// 自定义过滤方法
        /// </summary>
        /// <param name="text">找到的字符串</param>
        /// <param name="offset">起始位置</param>
        /// <param name="length">字符串长度</param>
        /// <returns>替换后的</returns>
        public delegate string ReplaceDelegate(string text, int offset, int length);


    }

[转载]android使用自定义属性AttributeSet - longvslove的专栏 - 博客频道 - CSDN.NET

mikel阅读(838)

[转载]android使用自定义属性AttributeSet – longvslove的专栏 – 博客频道 – CSDN.NET.

这里为了演示使用自定义变量,字体大小改用自定义的属性。

首先要创建变量,创建了个values/attrs.xml文件,文件名任意,但是要在values目录下:

<?xml version=”1.0″ encoding=”utf-8″?>
<resources>
<declare-styleable name=”button”>
<attr name=”textSize” format=”dimension” />
</declare-styleable>
</resources>

根标签要是resources,定义的变量要有个名字,declare-styleable name=”button”>,这里定义名称为button。在这个名称里,可以有多个自定义属性。定义了个名为textSize的属性,格式是 dimension,这个format指定了textSize属性的类型,只能用于定义字体大小。

在布局文件中通过自定义属性赋值:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:Android=”http://schemas.Android.com/apk/res/Android
xmlns:myapp=”http://schemas.android.com/apk/res/com.easymorse.textbutton”
android:orientation=”vertical” android:layout_width=”fill_parent”
android:layout_height=”fill_parent” android:background=”@drawable/background_color”>
<LinearLayout android:layout_width=”fill_parent”
android:layout_height=”10dip” />
<LinearLayout android:layout_width=”fill_parent”
android:layout_height=”40dip”>
<com.easymorse.textbutton.TextButton
android:layout_width=”fill_parent” android:layout_height=”fill_parent”
android:layout_weight=”1″ android:text=”电影”
android:gravity=”center_vertical|center_horizontal”
android:background=”@drawable/button” android:focusable=”true”
android:clickable=”true” myapp:textSize=”20sp” />

这里在根标签中增加了:

xmlns:myapp=http://schemas.android.com/apk/res/com.easymorse.textbutton

声明了myapp这个名字空间,myapp是任意的名称,自己可以随便起名,后面的:

http://schemas.android.com/apk/res/

是固定的。再后面接的是应用的包名。

在下面自定义按钮中的:myapp:textSize,就是使用<attr name=”textSize”这个变量了,给变量赋值。

还需要一个过程,就是在程序中获取到这个赋值:

public TextButton(final Context context, AttributeSet attrs) {
this(context, attrs, 0);
TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.button);
this.setTextSize(typedArray.getDimension(R.styleable.button_textSize, 15));
typedArray.recycle();

其中,TypedArray实例是个属性的容器,context.obtainStyledAttributes()方法返回得到。 AttributeSet是节点的属性集合,在本例中是<com.easymorse.textbutton.TextButton节点中的属性集 合。

这句话:

typedArray.getDimension(R.styleable.button_textSize,
15)

将获取自定义textSize的值,如果没有,则使用默认的值,15。

最后别忘记调用:

typedArray.recycle();

作用是:

Give back a previously retrieved StyledAttributes, for later re-use.

这里的自定义属性的format,可以有很多种:

  • reference
  • string
  • color
  • dimension
  • boolean
  • integer
  • float
  • fraction
  • enum
  • flag

[转载]Android使用AttributeSet自定义控件的方法 - 子午 - 博客园

mikel阅读(842)

[转载]Android使用AttributeSet自定义控件的方法 – 子午 – 博客园.

所谓自定义控件(或称组件)也就是编写自己的控件类型,而非Android中提供的标准的控件,如TextView,CheckBox等等.不过自定义的控件一般也都是从标准控件继承来的,或者是多种控件组合,或者是对标准控件的属性进行改变而得到的自己满意的控件.

   自定义控件可能会有很多种方法,这里只介绍我要介绍的方法.

   在这种方法中,大概的步骤是这样的

   1.我们的自定义控件和其他的控件一样,应该写成一个类,而这个类的属性是是有自己来决定的.

   2.我们要在res/values目录下建立一个attrs.xml的文件,并在此文件中增加对控件的属性的定义.

   3.使用AttributeSet来完成控件类的构造函数,并在构造函数中将自定义控件类中变量与attrs.xml中的属性连接起来.

   4.在自定义控件类中使用这些已经连接的属性变量.

   5.将自定义的控件类定义到布局用的xml文件中去.

   6.在界面中生成此自定义控件类对象,并加以使用.

   好了,按照上述的方法,我们来看看http://blog.csdn.net/Android_Tutor/archive/2010/04/20/5508615.aspx

   博客中的实例代码,按步骤加以解释:

   //———————————————————————————

   1. 定义自己的控件类:——————————————–代码1.

   package com.Android.tutor;  
   import Android.content.Context;  
   import android.content.res.TypedArray;  
   import android.graphics.Canvas;  
   import android.graphics.Color;  
   import android.graphics.Paint;  
   import android.graphics.Rect;  
   import android.graphics.Paint.Style;  
   import android.util.AttributeSet;  
   import android.view.View;  


   public class MyView extends View
   
        private Paint mPaint;  
       private Context mContext;  
        private static final String mString = “Welcome to Mr Wei’s blog”;  
     
       public MyView(Context context)
       
           super(context);  
           mPaint = new Paint();  
       


        public MyView(Context context,AttributeSet attrs)  
       
           super(context,attrs);  
           mPaint = new Paint();  
         
           TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.MyView);             
           int textColor = a.getColor(R.styleable.MyView_textColor,0XFFFFFFFF);  
           float textSize = a.getDimension(R.styleable.MyView_textSize, 36);  
         
           mPaint.setTextSize(textSize);  
           mPaint.setColor(textColor);  
         
           a.recycle();  
       }

  
       @Override
       protected void onDraw(Canvas canvas)

        
           // TODO Auto-generated method stub  
           super.onDraw(canvas);  
           //设置填充  
           mPaint.setStyle(Style.FILL);  
         
           //画一个矩形,前俩个是矩形左上角坐标,后面俩个是右下角坐标  
           canvas.drawRect(new Rect(10, 10, 100, 100), mPaint);  
         
           mPaint.setColor(Color.BLUE);  
           //绘制文字  
           canvas.drawText(mString, 10, 110, mPaint);  
       }
  
  

   代码1定义了一个自定义控件,名字为MyView,是从View类继承而来,也就是说它本身就是一种View,只是在View基础上加工而成了我们自己的自定义控件MyView.在此类种黄色的两行变量是我们新的属性变量.

   //———————————————————————————

   2. 在res/values目录下建立一个attrs.xml的文件,并在此文件中增加对控件的属性的定义–代码2:

   <?xml version=”1.0″ encoding=”utf-8″?>
   <resources>
       <declare-styleable name=”MyView“>
            <attr name=”textColor” format=”color” />
           <attr name=”textSize” format=”dimension” />
        </declare-styleable>
   </resources>

   在<resources>标签下使用<declare-styleable name=”MyView“>标签来告诉框架它包含的属性就是自定义控件MyView中的属性.黄色的两其实就对应了代码1中黄色的变量.

   //———————————————————————————

   3.使用AttributeSet来完成控件类的构造函数,并在构造函数中将自定义控件类中变量与attrs.xml中的属性连接起来.

   我们再看一下代码1中的蓝色代码,其中使用AttributeSet来重载构造函数.在此函数中将类中的属性变量与代码二中定义的属性联系起来.

    //———————————————————————————

   4.在自定义控件类中使用这些已经连接的属性变量.

   我们看一下代码1中的黄色部分,就是对我们新定义的属性的使用.

   //———————————————————————————

   5.将自定义的控件类定义到布局用的xml文件中去.—–代码3:

   我们再看看布局的xml文件代码:

   <?xml version=”1.0″ encoding=”utf-8″?>  
   <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android” 
       android:orientation=”vertical”
       android:layout_width=”fill_parent”
       android:layout_height=”fill_parent”  
       <TextView android:layout_width=”fill_parent”   
           android:layout_height=”wrap_content”   
           android:text=”@string/hello” />  

        <com.android.tutor.MyView  android:layout_width=”fill_parent”   
           android:layout_height=”fill_parent” test:textSize=”20px” test:textColor=”#fff” />  
    </LinearLayout>
   其中红色部分在布局中引用了我们MyView控件.

   //———————————————————————————

   6.在界面中生成此自定义控件类对象,并加以使用.——–代码4.

[转载]Android ListView下拉刷新、上拉载入更多 - 云水禅心的日志 - 网易博客

mikel阅读(1068)

[转载]Android ListView下拉刷新、上拉载入更多 – 云水禅心的日志 – 网易博客.
http://maxwin.me/blog/?p=154#header

SEO: Android ListView, Pull to refresh, Pull down to refresh. Pull up to refresh.

source code: https://github.com/Maxwin-z/XListView-Android

这是接触Android开发后写的第二个控件。话说下拉刷新组件网上就有好多版本,但我找到的两个(其中一个是 johannilsson的,国内很多就翻译的这个版本),效果都不是特别满意,关键还有bug(不至于crash,但是用户体验不好)

XListView,为毛叫X,因为它提供了“下拉刷新”和“上拉载入更多”两个功能,不想取PullDownRefreshAndPullUpLoadListView这种名字。

说下原理和碰到的问题,具体细节请到github下源码。

0. XListView继承ListView。

1. 下拉刷新组件是ListView的一个Header。在ListView创建时就将这个自定义View塞进去,默认情况是看不到的,所以这个HeaderView的高度初始设置为0。

2. 上拉载入更多组件是Footer,为了确保这个footer在最后(可能会添加多个自定义footer),在用户调用setAdatper的时候再把这个footer塞进去。

3. 覆写ListView的onTouchEvent方法,处理各种情况。

4. 用户松手,启动mScroller,将header、footer回滚到所需状态。

5. 添加了用户下拉、上拉移动delay的效果,类似iOS的行为。

6. 提供了两个接口:

a) IXListViewListener:  触发下拉刷新、上拉载入更多

b) OnXScrollListener: 这个和原生的OnScrollListener一样,但是在mScroller回滚时,也会触发这里的事件。

碰到的问题:

1. 用户下拉回推时,不断修改header的高度,但这时候滚动条指示器的位置还是按老的高度计算的,需要强制调用一下setSelection(0)将ListView滚动到顶部。

 贴几张效果图:

下拉刷新

  

上拉载入更多

  

其它:

  1. 例子很好哦,不过有一个BUG,就是当最开始数据不能撑满屏幕的时候,也就是比如有1条,那么将ListView向上拉不会完美刷新,必须将底部的view拉出屏幕才可以刷新,但是这个操作会超过1次的刷新,描述的不是很清楚,但是可以测试下就知道了

    • 了解,这个bug我没太重视,考虑的是有分页数据的情况,一般第一页都会撑满整个屏幕。
      谢谢反馈,我修改下看看。

      问题解决了,在XListView的onTouchEvent方法中,将default的else去掉就可以了,大概在303行的位置

2、

您 好,您实现的这个效果是我所有找到过的类似 的实现效果中最好的一个,比起那些反复设置measure值的方法容易理解的多,忍不住要赞一个!不过还有个问题想要请教下,您在 onTouchEvent方法中更新headerView高度时,传递的参数我不理解:updateHeaderHeight(deltaY / OFFSET_RADIO); 为什么要除以一个1.8f 的值呢?您在注释中说到是iOS的样式,那么能解释一下么?谢谢!

  • 我懂了,应该是为了延缓下拉速度,达到合适跨度的手势控制效果,除以1.8应该是一个多次尝试的结果吧