一、场景
今天研究android 6.0 pdk目录下testingcamera时遇到了一个布局文件的问题,本人是做底层的,研究了1个多小时才搞明白,特记录下笔记。如果有理解不到位的地方,欢迎指正,大家一起学习。
在一开始打开TestingCamera应用进行preview时,屏幕上只有一个preview预览界面。如下图所示。pdk的源码在链接
当我打开CallbackProcess时,屏幕上出现了2个预览界面,但是它是怎么重新布局的呢。在查看了布局xml文件之后,更是疑惑,下面我们结合代码来学习一下。
二、代码分析
1.SurfaceHolder.Callback接口介绍
我们知道如果activity实现了SurfaceHolder.Callback接口那么就会响应SurfaceCreated,SurfaceChanged,SurfaceDestroyed消息处理函数。他们触发的时机如下所示
SurfaceCreated:字面意思说的很明白,就是surface创建的时候触发
SurfaceChanged:同上面一行好理解,当surface的一些属性改变时触发,比如布局属性,size等
SurfaceDestroyed:当surface销毁时触发。
下面是每一个接口的官方解释,我就不细说了。
/**
* A client may implement this interface to receive information about
* changes to the surface. When used with a {@link SurfaceView}, the
* Surface being held is only available between calls to
* {@link #surfaceCreated(SurfaceHolder)} and
* {@link #surfaceDestroyed(SurfaceHolder)}. The Callback is set with
* {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
*/
public interface Callback {
/**
* This is called immediately after the surface is first created.
* Implementations of this should start up whatever rendering code
* they desire. Note that only one thread can ever draw into
* a {@link Surface}, so you should not draw into the Surface here
* if your normal rendering will be in another thread.
*
* @param holder The SurfaceHolder whose surface is being created.
*/
public void surfaceCreated(SurfaceHolder holder);
/**
* This is called immediately after any structural changes (format or
* size) have been made to the surface. You should at this point update
* the imagery in the surface. This method is always called at least
* once, after {@link #surfaceCreated}.
*
* @param holder The SurfaceHolder whose surface has changed.
* @param format The new PixelFormat of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
/**
* This is called immediately before a surface is being destroyed. After
* returning from this call, you should no longer try to access this
* surface. If you have a rendering thread that directly accesses
* the surface, you must ensure that thread is no longer touching the
* Surface before returning from this function.
*
* @param holder The SurfaceHolder whose surface is being destroyed.
*/
public void surfaceDestroyed(SurfaceHolder holder);
}
2.激发SurfaceChanged消息
针对TestingCamera 应用来说,当我使能callbackstream时,由于要显示callback 帧数据,所有总共有2个surfaceiew需要显示。如下当我点击CallbackOn按钮时响应的消息处理函数。
private View.OnClickListener mCallbackToggleListener =
new View.OnClickListener() {
public void onClick(View v) {
if (mCallbacksEnabled) { /*默认为false*/
log("Disabling preview callbacks");
stopCallbacks();
mCallbacksEnabled = false;
resizePreview();
mCallbackView.setVisibility(View.GONE);
} else {
log("Enabling preview callbacks");
mCallbacksEnabled = true;
resizePreview(); /*字面意思看,它会重新设置preview的大小,这就激发了SurfaceChanged方法*/
mCallbackView.setVisibility(View.VISIBLE);
}
}
};
很意外的是在resizePreview方法中,将CallbackSurfaceView的资源布局对象赋给了previewSurfaceView对象,这会触发SurfaceChanged消息。在SurfaceChanged方法中又会调用layoutPreview方法,重新设置preview和callbackSurface对象的布局参数。
void resizePreview() {
// Reset preview layout parameters, to trigger layout pass
// This will eventually call layoutPreview below
Resources res = getResources();
mPreviewView.setLayoutParams(
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0,
mCallbacksEnabled ? /*因为这里我已经使能了callback功能,这里为true*/
res.getInteger(R.integer.preview_with_callback_weight):
res.getInteger(R.integer.preview_only_weight) ));
}
/*SurfaceCahnged消息处理方法中,会调用layoutPreview方法,重新设置previewSurfaceView和CallbackSurfaceView对象的布局参数*/
void layoutPreview() {
int width = mPreviewSizes.get(mPreviewSize).width;
int height = mPreviewSizes.get(mPreviewSize).height;
float previewAspect = ((float) width) / height;
int viewHeight = mPreviewView.getHeight();
int viewWidth = mPreviewView.getWidth();
float viewAspect = ((float) viewWidth) / viewHeight;
if ( previewAspect > viewAspect) {
viewHeight = (int) (viewWidth / previewAspect);
} else {
viewWidth = (int) (viewHeight * previewAspect);
}
mPreviewView.setLayoutParams(
new LayoutParams(viewWidth, viewHeight));
if (mCallbacksEnabled) {
int callbackHeight = mCallbackView.getHeight();
int callbackWidth = mCallbackView.getWidth();
float callbackAspect = ((float) callbackWidth) / callbackHeight;
if ( previewAspect > callbackAspect) {
callbackHeight = (int) (callbackWidth / previewAspect);
} else {
callbackWidth = (int) (callbackHeight * previewAspect);
}
mCallbackView.setLayoutParams(
new LayoutParams(callbackWidth, callbackHeight));
configureCallbacks(callbackWidth, callbackHeight);
}
}
3.布局文件main.xml介绍
首先要介绍几个宏
1)fill_parent
设置一个构件的布局为fill_parent将强制性地使构件扩展,以填充布局单元内尽可能多的空间。这跟Windows控件的dockstyle属性大体一致。设置一个顶部布局或控件为fill_parent将强制性让它布满整个屏幕。
2) wrap_content
设置一个视图的尺寸为wrap_content将强制性地使视图扩展以显示全部内容。以TextView和ImageView控件为例,设置为wrap_content将完整显示其内部的文本和图像。布局元素将根据内容更改大小。设置一个视图的尺寸为wrap_content大体等同于设置Windows控件的Autosize属性为True。
3)match_parent
Android2.2中match_parent和fill_parent是一个意思 .两个参数意思一样,match_parent更贴切,于是从2.2开始两个词都可以用。那么如果考虑低版本的使用情况你就需要用fill_parent了
<!--第一层LinearLayout控件
如文章开始时,看到的图片中,可以发现第一层LinearLayout控件是填满名屏幕的
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<!--第二层LinearLayout控件-->
<LinearLayout
android:id="@+id/preview_column"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="6"
android:animateLayoutChanges="false"
android:orientation="vertical"
android:visibility="visible" >
<SurfaceView
android:id="@+id/preview"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="@integer/preview_only_weight"
tools:ignore="NestedWeights" />
<SurfaceView
android:id="@+id/callback_view"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="@integer/preview_with_callback_weight"
android:visibility="gone" />
<TextView
android:id="@+id/log"
android:layout_width="fill_parent"
android:layout_height="10dp"
android:layout_weight="1"
android:freezesText="true"
android:minLines="3"
android:typeface="normal" />
</LinearLayout>
<ScrollView
<!-- 这一部分就是下图中3部分的,控件的布局文件,这里就不贴上来了,感兴趣可以自己查看源代码
--></ScrollView>
</LinearLayout>
下图就是上图xml文件的效果,其中控件布局如下
标号1:该层第2个LinearLayout控件,包含其中的控件2和控件3是水平排列的。
标号2:该层是第2个LinearLayout控件,包含其中的控件4,5,6是垂直排列的。
标号4:previewSurceView控件
标号5:CallbackSurceView控件
标号6:TextView控件所在区域
为何是“android:layout_width="0dp" “
如果我们想按着空间的权值来分配宽度或者高度时,需设置对应的宽,高为0dp,如果为水平方向按比例分配的话,需设置android:layout_width="0dp",如果为竖直方向的设置android:layout_height="0dp"。在这种情况下某子个控件占用LinearLayout的比例为:本控件weight值 / LinearLayout内所有控件的weight值的和。下图中的4,5,6控件是垂直排列的,但是控件6的高度是10dp,控件4和控件5的高度都设置的是0dp,那这该怎么按比例分呢。我这里猜想如果有一个控件已经指定了宽或高,那么剩下的设置宽或高为0dp的控件会按比例分配宽高,那么控件4,5的高度就是(LCD_height - 控件6_height) / 2.
后续会细致学习一下android app