「读知乎」修复版

自从我把系统升级到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(右下)


寒冷的夏天


我醒来了,发现自己躺在图书馆前面的草坪中,旁边停着我的自行车,看看时间已经夜里一点半了,在这快睡了一小时。以前每次路过草坪时总会看见有人惬意的躺在上面,阳光浇洒在身上,展现出一种很享受的姿态,我在想终有一天我也要躺它一躺,今天终于实现了,却是在四年后的一个不见人影的深夜里。草坪完全不像它看上去的那般柔软,坚硬的草梗扎着背很难受,对此我有些失望,还是回去算了。

在入睡之前我是打算看月亮的,在滚滚的鳞片状的层云中 ,硕大的月亮时隐时现,我仰着脖子 看了好久,感觉太累就躺下了,之后便思绪万千胡乱瞎想起来。我是如此的年轻,却总感到老之将至,好像从身边悄悄溜走的不是现在,而是未来,未来她已经提前来了。月亮极美,有着诱人的魔力,但终究是不属于我的,我要她也没什么用呀,但她在我心中是如此的唯一,而我于她和那茫茫万千人毫无二致,毫无生机,我就这样重复着,单调的重复着,不是重复自己就是重复他人,而我却总是自以为是的活着。这样的夜,难免不让人想念生死。

起初我只是打算出来逛逛,因为从没有瞧过凌晨的学校,也没瞧过凌晨的湖。夜凉似水,我在路灯下没命的骑车,没有人,一个人也没有,这怎叫我不兴奋,我不总是渴望这样没人的世界吗?我路过可爱的湖,看着鱼儿在水中翻腾换气,长椅上躺着一位乞丐,他是每天都来吗?我在主楼前面的空地上转了又转,转了又转,突然感到一股莫名的寒意涌上心头,算来我已经有半年没出过校门了,我已经有四年没出过校门了,我已经有二十多年都没走出过自己的世界了,就这样悄悄的活着,死了也就死了,谁又会知道呢?这就是我的全部人生 !

后来,我又沿着操场穿过大礼堂绕过校医院,斑驳横生的树影让我感到如入幽境,然后路过图书馆接着转到大草坪直奔女生公寓,这么晚了还有人没睡哇,我挤过澡堂前的窄道,又从男生公寓沿着去食堂的路绕到了图书馆,于是就在图书馆前的草坪上躺下了。

本来打算在这草坪上过个夜,但依古人所言:乘兴而来,兴尽而返,所以,还是回去算了。