请把你的微笑留下

一首歌上来,像喝了一口烈酒,胃里翻江倒海,惦记着,我他妈到底什么时候才能够正经地写一篇记叙文,白描也行,没有味道也不要紧,重要的是要打破高中语文老师给我下的该死的死咒,这死咒箍的我头疼,箍的我蛋疼,箍的我一天到晚傻不拉几的,如冰柜里雪白的裸体鸡,脖子老弯向下体。

心里难平我便会看看星空,好让自己明白,无论我傻缺到天上还是傻缺到地里,都不重要,相对这浩淼的宇宙,地球都显得格外傻缺,上面的人类就不提了。如果那时是白天或者是晚上但有浓云,我就试着回忆对着银河数星星的日子,不一样的方法,但有着相同的目的。那时正值秋夜,没有奶奶温柔的怀抱,我蹲在厕所涨红了脸,汗流通背,咬着牙,攥紧拳头,奋力苦战最后几分钟,死命把大便往直肠前面拱,差不多时,突然卡壳了,我临危不乱,大吼一声,把拳头大小的屎厥子逼出我的身体。由于太过用力,导致心跳加速,眼睛开始迷乱起来,一抬眼便看到了猎户座,这名腰里别刀的野男子,老师说那是刀,专门砍偷他女人的男人和女人,但后来我又听到了其他说法,一说是剑,一说是腰带,但都没有和我想的一样的,我怎么看怎么觉得那像斜伸的大屌。快二十年过去了,我依然只认识猎户座,每次我和姑娘指出哪个是猎户座,她们便说,哇,阿贵好厉害,真浪漫……再用个什么星图之类软件,她们都要飞升起来,恨不得把我揽入怀抱,然后拍死,接着钉入猎户座,成为她们永久的星座。猎户座是我肛门永远的红肿,面对整个星空,我就只认识这个红肿星座,从头到尾一点都不浪漫,这说明浪漫都是用来骗傻缺的,都是一群什么人啊。

刚在听的歌是《把悲伤留给自己》,老歌,第一次听时比接触红肿星座还要早。当时听不懂,只觉得开头的口琴很好听,但至今我不会吹像样的曲子,我什么乐器都不会,我什么都不会,有人曾经跟我说你不会弹琴不会画画不会写诗不会搞对象,怎么还有勇气活下去。我说,我会吹竖笛。他说我故意要激怒他,然后疯狂的反驳,说,你傻缺,你有毛病,竖笛是屎,竖笛是癌,竖笛是你傻缺的命脉,你得癌了,你要去医院接受治疗,最好开个刀,在脑壳子里。这种说法来得早,扎的深,我开始认为搞音乐的搞艺术的都屌炸天,有气质,个个忧郁,能够徒步千里不拉屎。后来,我发现了真相,大部分人都傻缺,搞艺术的也不例外,该傻缺的比傻缺还傻缺,都是一群什么人啊。当然,我真喜欢那些真屌炸天的,真有气质的,真忧郁的,真徒步千里不拉屎的,你们都是我心中的偶像,血液里的活佛,你们丰富了我的生活,让那些虚伪的傻缺滚一边去吧。

前两天刚重听了另一首歌,一个和《把悲伤留给自己》这首歌的作者搞过暧昧同时也是他徒弟的一名女歌手唱的,这首歌最开始是在电影《天下无贼》中听到的,歌名叫《知道不知道》。曲子很老了,但新填的歌词很动人,每每听来,便如漫步于夏日的海边,沐浴着晚风,层层涌起小浪涛轻轻拍打我的小心肝,一不小心泪腺便失禁了,哗啦啦的流个不停。

我在想为什么没有暧昧这种单独的感情,不是说不存在,而是为什么不能被正常的广大人民群众所接受,现在不是讲究专业精分嘛,英特尔芯片流水线都搞到逆天的24步了,为什么可接受的感情还这么二进制。让我们来研究研究暧昧,什么叫暧昧,初次相遇,最好是偶遇,电梯,花园,咖啡馆入口处等旮旯,空间越逼仄越好,都有老公老婆男友女友就更好,聊了两句,甚是欢喜,没想到在这傻缺遍地的地方竟能遇到活宝,遇到活宝后其他人就更傻缺了。聊的越开心,接下来暧昧的程度越大。聊星座,聊书,聊电影,聊经济,聊历史,最好都能聊聊偏冷门的东西,这将极大的丰富你在对方眼里不是傻缺的可能,没有什么他妈是不能聊的,连对话本身都可以聊。聊着聊着便开始图不轨了,想把对方捧入手心,变成自己的红肿星座,想一直聊下去,聊到睡着,醒来后接着聊,大便的时候也不怕熏到对方。临走时,约好,下次接着聊,最好是一个新的地方,最老的地方是分手用的。晚上睡不着,旁边有自己的老公甚至有孩子也不踏实,就是睡不着,开始操心红肿星座怎么样了,于是就发短信问,你睡着了吗?短短几个字,融入无限的温柔,老公做梦都想不到的温柔,像彗星的彗尾,弥散在夜空中。十分钟没回,二十分钟还没回,开始流汗,坐起来,挠挠头,重新对了下号码,发现写错了,开心极了,仿佛收到了红肿星座的回复一样,一开心,便又睡着了。第二天见了面,先矜持,仿佛隔了数年未见似的,女方用手把头发撩到耳朵后面,怎么温柔怎么来,男方则礼貌大方,双方开始聊天,双方开始傻笑,聊天和傻笑是并行运算的。仿佛每句话都饱含笑点,别人都用看傻缺的眼神看着你们,你们无视他们。聊的内容不能和昨天太重合,这意味着你得懂很多,不然就像是干射,很容易被对方识破你就那么点存货,女方会因为这点而向你脸上泼咖啡——臭流氓,男方会借着去洗手间的时侯从后门偷偷溜走。总的来说,这一次又聊的很开心,和昨日相比开心程度有所减少,但深入的知足感又弥补了这一点,甚至升华了这一感觉,你们感受到了很多,那种砰砰砰心跳的感觉真是奇妙,像鸦片,吸食之后觉得对方金光闪闪,彼此像虎符的另一半,咬合程度精确到爆,你想和他或她融为一体了,你想开始不要脸了,当然,也只是想。你们像是站在高海拔缺氧的山脊看着纷乱的一切,笑着一个个男男女女找不到另一个个男男女女,你们怎么就这么幸运,就这么仓惶的相遇,虽然太晚又太早,为此,你们有时候都恨自己。正聊得开心时,这时女方接到一个电话,是老公打来的,我今天有会,你晚上去接一下孩子,声音有点大,对面的红肿星座也听到了,露出傻缺的表情,这一瞬间你们感觉像是被剥光了衣服,赤条条的站在高海拔缺氧的山脊。后来,你们一起吃饭,你们一起看电影,你们一起杀人一起放火,你们一起干了所有你们认为不应该干却又不得不干的事情。后来,你们相互发现也就这样了,也就只能这样?你们望着灰色的天空,无助地如此安静,细细的回忆发生的一切,聊天再有深度又能如何,能发论文不?一开始再开心又如何,是可持续发展吗?如果不是可持续发展,能很快就死掉吗?当又开始想和其他人搞暧昧时怎么搞?怎么搞?如果和最开始的那个没分手就尼玛四角恋了,打麻将都够凑一桌了,你们开始觉得自己怎么就这么贱这么傻缺呢,你爱我我爱你你不爱我我不爱你这些乱七八糟的一切都有什么鸡巴意思?当然,这个很难想得通。后来,你们突然发现,暧昧不好,暧昧不好,二进制最完美,要么爱,要么滚蛋。

我睡不着,看书也睡不着,看我看不懂的书也睡不着。最近有很多五年前八年前就应该思考的问题都灌入我的大脑中,来的更猛,持续的更久,我没办法,只得起身迎战。如今不是秋天,没有红肿星座可看,天空雾霾严重,连站牌都看不清,更不用谈彩霞和星空了。这些问题具体我说不清楚,都是一些关于人,关于风,关于脚趾头,关于农夫山泉,关于一切的鸡巴问题。有的人想通了,然后出家了,有的人想不通,然后也出家了,有的人还没想就跳楼了,有的人醉了,有的人疯了,有的人谈了17场恋爱,每场都如初恋,有的人搞科学,在成就梦想的时候也成了刽子手,成了刽子手后又大喊我不要当刽子手我不要当刽子手,有的人在搞学术,有的人自认为在搞学术,有的人打扫大街,日复一日,有的人吃包子,碗口大的包子,一顿5个,有的人把污水偷偷排入江里,他们夜观星空也发现了自己的渺小,有的人说谎,有的人不说谎却傻缺的可以,有的人感慨今天好多人从他身边擦身而过,明天相同的人几乎没有,于是开始伤感,有的人旅游,大谈旅游很好玩,脱困解乏,比大保健来的还要巧妙,有的人不开心却说不出原因,有的人瞧不起别人,有的人瞧不起所有人,有的人想干一票就走,有的人想干十票而名垂千古,有的人是人,有的人不是人,人不是人。而这天我什么也干不了,我只能在酷暑中流汗,在酷暑中喝水,在酷暑中大便,躺在床上,我想着生想着死,我想,对于别人来说,我也只是有的人。

下面这首《歌声与微笑》是我能够正确识别红肿星座的前几年学的,教我们唱歌的是一个顶着锅盖头的女同学,很单纯,虽然我曾经知道单纯为何物,但是,聪明的,你告诉我,我们的单纯为什么一去不复返呢?

请把我的歌带回你的家。
请把你的微笑留下。
请把我的歌带回你的家,
请把你的微笑留下。
明天明天这歌声,
飞遍海角天涯,飞遍海角天涯。
明天明天这微笑,
将是遍野春花,将是遍野春花。

「读知乎」修复版

自从我把系统升级到MIUI 6(擦,暴露了)后「读知乎」就不能用了,每次进去之后都像一坨小鸡那样卡死在那里。原来的项目因为版权问题已经停止更新了,因此,我擅作主张修复了一些让我不愉快的bug,并增加了图片放大功能。下面的apk是没有签名的,所以要安装得先删掉之前的官方版本。点击下面图标下载。

Android Android

其实我什么都不懂

clipToPadding

ListView和GridView有属性clipToPadding可以设置padding值

内存回收

可以调用System.gc()(和java中是否一样?)。但一般由系统调用。
问题:如何避免垃圾回收产生的卡顿?

Math.round(),Math.floor(),Math.ceil()

round() 四舍五入,11.5->12, -11.5->-11
floor() 地板向下取整,11.6->11, -11.3->-12
ceil() 天花板向上取整,11.2->12, 11.0->11

Android四大组件

每个应用是个用户,有一个独立的进程,当不需要时或内存不够时被杀死。
It’s possible to arrange for two apps to share the same Linux user ID, in which case they are able to access each other’s files. To conserve system resources, apps with the same user ID can also arrange to run in the same Linux process and share the same VM (the apps must also be signed with the same certificate).
每个组件都是一个入口,生命周期不一样

service bind神马的

如果调用startService启动服务,那么同时可以调用bindService绑定服务,但当执行unbind并不能停止服务,要调用stopSelf()或者stopService()
如果执行onBind绑定Service有三种方法,第一种适用于在同一进程内,第二种和第三种用于不同进程

binder

ContextManager 的handle约定为0,这样就很容易找到他,然后Service Server把自身下的服务(A,B,C…)编号为(101,102,103…),通过IPC注册到ContextManager中,由于设置handle=0,在BinderDiver中直接连接到ContextManager,比如(1,A;2,B;3,C…)。
客户端请求时最终是要找到ServiceServer下的服务,但是由于只知道名字,假设是服务是B,于是就发送handle=0,服务=B到BinderDriver,BinderDriver转到ContextManager查找到B的值为2,又通过BinderDriver的服务节点列表,插得Service Server中B的编号是102,于是把102返回。然后完成寻址后,客户端设置handle=102,加上函数,参数,以及代号,直奔ServiceServer下的服务B

AIDL

如果需要不同客户端通过IPC来访问service,以及处理多线程则考虑AIDL,不然使用Binder

View

inflate(int resource, ViewGroup root, boolean attachToRoot)返回一个rootView,如果参数root!=null,那么返回的就是root,otherwise it is the root of the inflated XML file.如果如果attachToRoot=false,那么解析的view就不会使用root提供的LayoutParams,那么参数root会为返回的rootview提供LayoutParams。

Gradle

如果是jar,在module中添加libs文件夹,拷贝.jar文件到libs中,然后在dependencies中添加

compile fileTree(dir: 'libs', include: ['*.jar'])

如果是android lib,import module,在dependencie中添加

compile 'com.daimajia.easing:library:1.0.0@aar'

错误

如果findViewById return null,检查View的三个构造函数,确保能解析xml

Camera

可以用来过滤那些不含相机的手机,required=false,不含相机也可以装应用

 public static void setCameraDisplayOrientation(Activity activity,
         int cameraId, android.hardware.Camera camera) {
     Camera.CameraInfo info =
             new Camera.CameraInfo();
     Camera.getCameraInfo(cameraId, info);
     int rotation = activity.getWindowManager().getDefaultDisplay()
             .getRotation();
     int degrees = 0;
     switch (rotation) {
         case Surface.ROTATION_0: degrees = 0; break;
         case Surface.ROTATION_90: degrees = 90; break;
         case Surface.ROTATION_180: degrees = 180; break;
         case Surface.ROTATION_270: degrees = 270; break;
     }

     int result;
     if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
         result = (info.orientation + degrees) % 360;
         result = (360 - result) % 360;  // compensate the mirror
     } else {  // back-facing
         result = (info.orientation - degrees + 360) % 360;
     }
     camera.setDisplayOrientation(result);
 }

camera方向不要想多了,文档有问题!
有四个方向:
CameraInfo.orientation = 90//一般是这样的
device.orientation
camera.setDisplayOrientation(int )//设置预览和定格的照片
parameters.setRotation(int )//设置最后储存的照片
把相机想象成和手机一样的物体,在竖屏下device.orientation=0,但是照相机转了90°(不能变),我们可以把竖屏变成横屏device.orientation=90,这时手机屏幕和相机重合了,自然可以正常显示了。另一方面,我们把手机屏幕里的图片转90°,然后相机把照片拍在上面,然后再转回来,就能正常显示了,所以设置后两个为90°。

读取camera支持的图片尺寸

Camera.Parameters mParameters = mCamera.getParameters();
   Camera.Size bestSize = null;

   List sizeList = mCamera.getParameters().getSupportedPreviewSizes();
   bestSize = sizeList.get(0);

     for(int i = 1; i < sizeList.size(); i++){       if((sizeList.get(i).width * sizeList.get(i).height) >
        (bestSize.width * bestSize.height)){
       bestSize = sizeList.get(i);
      }
     }

     mParameters.setPreviewSize(bestSize.width, bestSize.height);
     mCamera.setParameters(mParameters);

Android 网络

模拟器访问电脑的IP 10.0.2.2

Fragment

add是把一个fragment添加到容器里面,view重叠在一起,就像图层一样。
android.R.id.content

GSON在嵌套情况下

ContentBean contentBean = null;
Type type = new TypeToken(){}.getType();
Gson gson = new Gson();
contentBean = gson.fromJson(json, type);

intent

隐式的intent用来启动其他软件的组件,系统通过查看所有组件的intent-filter来决定是否启动。
To ensure your app is secure, always use an explicit intent when starting a Service and do not declare intent filters for your services.
如果没有指明content name,系统会从intent的其他属性如action,category,data来决定如何启动

ScaleType

http://jameszhao84.iteye.com/blog/1397611

图片大小。宽*高=800*1200
ImageView 大小 400*400

  • CENTER

裁剪不缩放,把图片最中间400*400的区域显示出来

  • CENTER_CROP

裁剪缩放,把图片最中间800*800的区域放到400*400上,要缩小
将图片等比例缩放,让图像的短边与ImageView的边长度相同,即不能留有**空白**,缩放后截取中间部分进行显示。

  • CENTER_INSIDE

不裁剪缩放,把整个图片800*1200的区域,放到ImageView的中间,宽为(800/3)高为400
如果是小图,则直接放在中间
可以有空白

  • FIT_CENTER

保持图片比例,至少有一边挨着边缘,如果图片大则缩小,图片小则放大

  • FIT_END
  • FIT_START
  • FIT_XY

撑满ImageView,可以变形

  • MATRIX

从左上角开始,不变形,能显示多少显示多少,可以使用imageview的matrix

延迟获得view属性

http://stackoverflow.com/questions/3591784/getwidth-returns-0

tv.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
           public void onLayoutChange(View v, int left, int top, int right, int bottom, 
                                      int oldLeft, int oldTop, int oldRight, int oldBottom) {
                        final int width = right - left;
                        System.out.println("The width is == " + width);                
    });

tv.post(new Runnable(){
    run() {
    }
});

yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once :
            yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

            // Here you can get the size :) 
        }
    });

view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        view.getHeight(); //height is ready
    }
};

如果需要获取某个View的宽高,可以在onWindowFocusChanged()处直接获取即可

Anyhow, the deal is that layout of the contents of a window happens after all the elements are constructed and added to their parent views. It has to be this way, because until you know what components a View contains, and what they contain, and so on, there’s no sensible way you can lay it out.

Bottom line, if you call getWidth() etc. in a constructor, it will return zero. The procedure is to create all your view elements in the constructor, then wait for your View’s onSizeChanged() method to be called — that’s when you first find out your real size, so that’s when you set up the sizes of your GUI elements.

Be aware too that onSizeChanged() is sometimes called with parameters of zero — check for this case, and return immediately (so you don’t get a divide by zero when calculating your layout, etc.). Some time later it will be called with the real values.

listview中多个type时

getViewType必须从0开始

ImageView 需要裁剪的与setPadding冲突

CENTER_CROP和padding冲突
CENTER和padding冲突

LogCat过滤

tag:^(?!.*(DeskClock|dalvik|wpa)).*$
^(?!(dalvikvm|AbsListView|Xmpp|SnsService|Json))

底部画线

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:bottom="0dp"
android:left="-4dp"
android:right="-4dp"
android:top="-4dp">
<shape android:shape="rectangle" >
<stroke
android:width="2dp"
android:color="#1DAADE" />
</shape>
</item>
</layer-list>

键盘遮挡

在外面套一层ScrollView,并设置fillViewport=true

fragment onResume onPause

	@Override
	public void setUserVisibleHint(boolean isVisibleToUser) {
		super.setUserVisibleHint(isVisibleToUser);
		if (isVisibleToUser) {
			getDataFromDb();
		} else {
			// 相当于Fragment的onPause
		}
	}

获取fragment

RecommendFriendFragment tribeFragment = (RecommendFriendFragment) getSupportFragmentManager()
				.findFragmentByTag("user");
if (tribeFragment == null) {
	tribeFragment = new RecommendFriendFragment();
}

getSupportFragmentManager().beginTransaction()
		.replace(android.R.id.content, new RecommendFriendFragment(), "user").commit();

//弹出:
getFragmentManager().popBackStack();,这样下次再进要重新刷新,使用上面的方法可以避免刷新

有多个Fragment,退出

	@Override
	public void onBackPressed() {
		// TODO Auto-generated method stub
		if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
			this.finish();
			return;
		}
		super.onBackPressed();
	}
 fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if(getFragmentManager().getBackStackEntryCount() == 0) finish();
        }
    });

每次replace会调用Fragment的onCreatView方法

//要移除parent 才能重新添加
if (windowLayout == null) {
		initTitleBar();
		contentLayout.setClickable(true);
		addChildView(contentLayout);
	} else {
		((ViewGroup) windowLayout.getParent()).removeView(windowLayout);
}

filter.addAction在registerReceiver之前

之后注册的动作没有效

IntentFilter filter = new IntentFilter();
filter.addAction(DynamicHelper.ACTION_REFRESH_DYNAMIC);
getActivity().registerReceiver(mReceiver, filter);

动态获取resid

int id = getResources().getIdentifier("icon_welcom_background_" + i, "drawable",
				MainActivity.this.getPackageName());
return getResources().getDrawable(id);

findViewById找不到自定义View

请确保使用以下格式

	public ProgressView(Context context) {
		super(context); 
		init();
	}
	public ProgressView(Context context, AttributeSet attrs) {
		super(context, attrs); 
		init();
	}
	public ProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init();
	}

而不要使用

	public ProgressView(Context context) {
		this(context, null);
	}
	public ProgressView(Context context, AttributeSet attrs) {
		this(context, null, 0);
	}
	public ProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init();
	}

嵌套view点击无效果

确保在这个view上面且与view重叠的其他view没有加上按钮监听,如果在父viewgroup上有OnLongClick,那么子View的onClick也无法执行

删除列表中某个内容

//可以用
for (int i = 0; i < zanList.size(); i++) {
	if (zanList.get(i).uid.equals(mLogin.uid)) {
		zanList.remove(i);
		return;
	}
}
//老是删除第一个,不知道为什么?????????????
for (MessageInfo messageInfo : zanList) {
	if (messageInfo.uid.equals(mLogin.uid)) {
		zanList.remove(messageInfo);
		return;
	}
}

取消点击效果时可设置setEnabled(false)

在service中使用SharePreference时使用Muti_Process(sdk>11)模式

edittext在scrollview中无法滚动

EditText EtOne = (EditText) findViewById(R.id.EditText01);
    EtOne.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (v.getId() == R.id.comment1) {
                v.getParent().requestDisallowInterceptTouchEvent(true);
                switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_UP:
                    v.getParent().requestDisallowInterceptTouchEvent(false);
                    break;
                }
            }
            return false;
        }
    });

ActivityB设置android:launchMode=”singleTask”后,ActivityA startActivityForResult启动B时立刻调用A的onActivityResult。但是如果C正常那么B启动C时没影响

notificationManager.notify(NOTIFYD_SYSTEM, builder.build())

如果点击通知后没反应,更改NOTIFYD_SYSTEM的值

Edittext切换密码输入

Password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);

.9图片无法居中

查看其尺寸是否大于限制尺寸,使其可拉伸并且可压缩

输入法之前

@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
	// TODO Auto-generated method stub
	if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP
			&& (backPressedListener != null)) {
		Logger.d(this, "back preIme");
		if (this.getVisibility() == View.VISIBLE) {
			hideSoftKeyboard(this);
			backPressedListener.onBack();
			return true;
		}
	}
	return super.dispatchKeyEventPreIme(event);
}

一个Activity在调用onStop不一定会在另一个Activity调用onResume之前啊。。。

Android task相关

http://developer.android.com/guide/components/tasks-and-back-stack.html

  • singleTop→如果在当前task的顶层则不再创建,并执行onNewIntent()
  • singleTask和singleInstance→在一个设备中只能有一个实例,并且是一个task的root。对于singleInstance,在这个task中有且只有一个实例,singleTask则允许这个task中拥有其它activity。
  • singleTask和Flag_new_task看起来是一致的。如果此Activity在某task中存在,则把整个task抬到最上面,并执行onNewIntent()。如果目标Activity没有设置taskAffinity按普通模式启动?
  • 通知栏消息跳转,使用Flag_new_task和Flag_clear_top,跳到singleTask的界面(为根界面),然后再进行消息分配。
  • taskAffinity必须包含一个dot。

Touch事件 相关

参看ViewGroup中dispatchTouchEvent源码。
如果是ACTION_DOWN,清空touchTarget,并设置mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT(置为0,咱可以拦截事件,子View可以在接下来的事件中不让该ViewGroup拦截事件)。此时mFirstTouchTarget为空,调用onInterceptTouchEvent检测是否拦截,如果拦截设置intercepted=true。如果拦截了(注意还是ACTION_DOWN),由于mFirstTouchTarget为空,所以调用dispatchTransformedTouchEvent,相当于调用此ViewGroup的onTouchEvent,如果返回的结果就是dispatchTouchEvent的返回结果。如果没拦截,直接返回false。

  • 考虑一个指头情况,在ACTION_DOWN中,如果有子View消费了ACTION_DOWN,那么接下来的事件会直接分发给该子View。如果没找到点击区域的子View,或者子View返回false,接下来的事件(ACTION_MOVE, ACTION_UP)会直接分发给本ViewGroup(也就是一个普通的View)。

ViewPager嵌套ViewPager

在ViewPager1中嵌套ViewPager2,如果不想让ViewPager2左右触摸滑动(只用点击tab切换),要在ViewPager1中重写下面方法

@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
	return false;
}

WebView添加回调本地方法时要加上以下注解(targetSdkVersion>=17需要)

@JavascriptInterface
public void showSource(String html) {
	Logger.d(this, html);
}

歌词字级同步——ProgressText

天天动听里的歌词是字级同步的,在Android中如何实现呢?首先它的字有一层不同颜色的描边,另一方面随着时间推进一种颜色会逐渐代替另一种颜色。

对于第一个问题把 TextView 中的 Paint 改成 getPaint().setStyle(Paint.Style.FILL_AND_STROKE) 是没有用的,因为使用的是和字体相同的颜色来描边。所以这里采用了一个笨办法,真是笨办法,但比用阴影、设置一大一小叠字要好:-)

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    setTextColor(strokeColor);
    getPaint().setStrokeWidth(strokeWidth);
    getPaint().setStyle(Paint.Style.STROKE);
    super.onDraw(canvas);
}

先把字画出来,然后用不同的颜色描下边就可以了。

对于第二个问题要使用到 BitmapShader ,它继承自 Shader,文档里这样描述的:

A subclass of Shader is installed in a Paint calling paint.setShader(shader). After that any object (other than a bitmap) that is drawn with that paint will get its color(s) from the shader.

Paint 绑定 Shader 后,用它画出来的东西颜色都是从 Shader 中来取,Shader 的大小可能比所要画的物体要小,就如铺地板一样,地板总比地面要小,所以 Shader 中引入三种铺砖模式:

  1. CLAMP——用Shader边上的颜色来画超出的范围,原地板是[l,M,r],铺出来是…[l][l][l,M,r][r][r]…
  2. REPEAT——按正常情况下重复,原地板是[L,R],铺出来是[L,R][L,R][L,R]…
  3. MIRROR——边镜像边重复,原地板是[L,R],铺出来是[L,R][R,L][L,R]…

这里设置一个2个像素的BitmapShader,左边和右边像素颜色不一样,设置x方向的铺砖模式为CLAMP,y方向的铺砖模式为REPEAT,然后移动 Shader ,移动可以通过设置 Matrix 实现:

shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, 
                                  Shader.TileMode.REPEAT);
matrix.setTranslate(pixels, 0);//左右移动pixels
shader.setLocalMatrix(matrix);

最后在先画 Shader,然后去掉 Shader 给字描边:

@Override
protected void onDraw(Canvas canvas) {
    getPaint().setStyle(Paint.Style.FILL);
    getPaint().setShader(shader);
    super.onDraw(canvas);

    getPaint().setShader(null);
    setTextColor(strokeColor);
    getPaint().setStrokeWidth(strokeWidth);
    getPaint().setStyle(Paint.Style.STROKE);
    super.onDraw(canvas);
}

具体实现见https://github.com/withparadox2/ProgressText,首先添加dependencies。
在Layout里声明:

在头部添加:xmlns:custom="http://schemas.android.com/apk/res-auto"
<com.withparadox2.progresstext.ProgressText
    android:id="@+id/progress_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="50sp"
    custom:after_progress_color="@android:color/holo_purple"
    custom:before_progress_color="#FFFF00"
    custom:stroke_color="@android:color/black"
    custom:stroke_width="1px"/>

然后就可以设置进度了:

textView = (ProgressText) findViewById(R.id.progress_text);
textView.setText("绿岛小夜曲");
textView.setProgressBypercentage(0.68f);
//textView.setProgressBypixels(150);

效果如下:

GrayHours

An implementation of 10k-hours theory.

basic rules and ideas

  • The number of goals or plans you want to set up are is limited to only four. More is helpless!
  • Once a goal is written down, you can not rename it or remove it unless you delete the whole app and then reinstall.
  • You are not allowed to record more details than the time consumed for a goal.
  • Checking the github-view like graph everyday will force you to fill every blank day.

screenshots

source code

https://github.com/withparadox2/GrayHours

牵手

Tips:w(上)s(下)a(左)d(右)q(左上)e(右上)z(左下)c(右下)