Android自定义组件系列【1】——自定义View及ViewGroup
View类是ViewGroup的父类,ViewGroup具有View的所有特性,ViewGroup主要用来充当View的容器,将其中的View作为自己孩子,并对其进行管理,当然孩子也可以是ViewGroup类型。
View类一般用于绘图操作,重写它的onDraw方法,但它不可以包含其他组件,没有addView(View view)方法。
ViewGroup是一个组件容器,它可以包含任何组件,但必须重写onLayout(boolean changed,int l,int t,int r,int b)和onMesure(int widthMesureSpec,int heightMesureSpec)方法. 否则ViewGroup中添加组件是不会显示的。
package com.example.testrefreshview; import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyViewGroup(this)); } public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) {
super(context);
Button button1 = new Button(context);
button1.setText("button1"); Button button2 = new Button(context);
button2.setText("button2"); TextView textView = new TextView(context);
textView.setText("textView"); addView(button1);
addView(button2);
addView(textView);
} @Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3,
int arg4) { }
}
}
View的layout(int left,int top,int right,int bottom)方法负责把该view放在参数指定位置,所以如果我们在自定义的ViewGroup::onLayout中遍历每一个子view并用view.layout()指定其位置,每一个子View又会调用onLayout,这就构成了一个递归调用的过程
如果在ViewGroup中重写onDraw方法,需要在构造方法中调用this.setWillNoDraw(flase); 此时,系统才会调用重写过的onDraw(Canvas cancas)方法,否则系统不会调用onDraw(Canvas canvas)方法.
将上面代码修改一下,就可以显示出来。
package com.example.testrefreshview; import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyViewGroup(this)); } public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) {
super(context);
Button button1 = new Button(context);
button1.setText("button1"); Button button2 = new Button(context);
button2.setText("button2"); TextView textView = new TextView(context);
textView.setText("textView"); addView(button1);
addView(button2);
addView(textView);
} @Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3,
int arg4) {
int childCount = getChildCount();
int left = 0;
int top = 10;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.layout(left, top, left + 60, top + 60);
top += 70;
}
}
}
}
再看一段代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int childCount = getChildCount();
//设置该ViewGroup的大小
int specSize_width = MeasureSpec.getSize(widthMeasureSpec);
int specSize_height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(specSize_width, specSize_height); for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
childView.measure(80, 80);
}
}
通过重写onMeasure方法不但可以为ViewGroup指定大小,还可以通过遍历为每一个子View指定大小,在自定义ViewGroup中添加上面代码为ViewGroup中的每一个子View分配了显示的宽高。
下面我们让子View动起来吧,添加代码如下:
public boolean onTouchEvent(MotionEvent ev) {
final float y = ev.getY();
switch(ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
int detaY = (int)(mLastMotionY - y);
mLastMotionY = y;
scrollBy(0, detaY);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
在上面用到了一个scrollBy方法,打开官方API可以看到View类有如下两个方法:
这两个函数貌似都是移动视图的,那么它们有什么区别呢?带着这个疑问我们向下看
首先 ,我们必须明白在Android View视图是没有边界的,Canvas是没有边界的,只不过我们通过绘制特定的View时对 Canvas对象进行了一定的操作,例如 : translate(平移)、clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 ,我们可以将这种无边界的视图称为“视图坐标”-----它不受物理屏幕限制。通常我们所理解的一个Layout布局文件只是该视图的显示区域,超过了这个显示区域将不能显示到父视图的区域中 ,对应的,我们可以将这种有边界的视图称为“布局坐标”------ 父视图给子视图分配的布局(layout)大小。而且, 一个视图的在屏幕的起始坐标位于视图坐标起始处,如下图所示。
由于布局坐标只能显示特定的一块内容,所以我们只有移动布局坐标的坐标原点就可以将视图坐标的任何位置显示出来。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#888888"> <TextView
android:id="@+id/txt"
android:layout_width="300dip"
android:layout_height="120dip"
android:background="#cccccc"
android:text="textview" /> <Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button" />
</LinearLayout>
当我点击按钮触发事件后如下:
上面代码中触发点击事件后,执行了textView.scrollTo(-200, -100);scrollTo中的两个参数的含义不是坐标位置,而是相对于视图坐标位置的偏移量,假设我们要移动到(200,100)的位置则偏移量为(0,0)-(200,100) = (-200, -100)。
假设我们将上面代码换成scrollBy则会发现点击多次按钮后textview就会移出可见界面,这是因为scrollBy是相对我们当前坐标进行偏移。
下面我们来看看源代码中对这两个方法是如何实现的:
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
invalidate(true);
}
}
}
可以看到 mScrollX = x; mScrollY = y;
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
可以看到 mScrollX + x, mScrollY + y;
mScrollX和mScrollY是相对于视图坐标的当前偏移量。
Android自定义组件系列【1】——自定义View及ViewGroup的更多相关文章
- Android自定义组件系列【7】——进阶实践(4)
上一篇<Android自定义组件系列[6]--进阶实践(3)>中补充了关于Android中事件分发的过程知识,这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpan ...
- Android自定义组件系列【6】——进阶实践(3)
上一篇<Android自定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计划 ...
- Android自定义组件系列【5】——进阶实践(2)
上一篇<Android自定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这一 ...
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
在上一篇文章<Android自定义组件系列[3]--自定义ViewGroup实现侧滑>中实现了仿Facebook和人人网的侧滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布局示 ...
- Android自定义组件系列【3】——自定义ViewGroup实现侧滑
有关自定义ViewGroup的文章已经很多了,我为什么写这篇文章,对于初学者或者对自定义组件比较生疏的朋友虽然可以拿来主义的用了,但是要一步一步的实现和了解其中的过程和原理才能真真脱离别人的代码,举一 ...
- Android自定义组件系列【2】——Scroller类
在上一篇中介绍了View类的scrollTo和scrollBy两个方法,对这两个方法不太了解的朋友可以先看<自定义View及ViewGroup> scrollTo和scrollBy虽然实现 ...
- android 应用架构随笔四(View、ViewGroup)
View表示了用户界面的基本构建模块. 一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理.手机屏幕上所有看得见摸得着的都是View. Activity是四大组件中唯一一个用来和用户进行 ...
- Android自定义组件系列【12】——非UI线程绘图SurfaceView
一.SurfaceView的介绍 在前面我们已经会自定义View,使用canvas绘图,但是View的绘图机制存在一些缺陷. 1.View缺乏双缓冲机制. 2.程序必须重绘整个View上显示的图片,比 ...
- Android自定义组件系列【17】——教你如何高仿微信录音Toast
一.Toast介绍 平时我们在Android开发中会经常用到一个叫Toast的东西,官方解释如下 A toast is a view containing a quick little message ...
随机推荐
- Five ways to maximize Java NIO and NIO.2--转
原文地址:http://www.javaworld.com/article/2078654/java-se/java-se-five-ways-to-maximize-java-nio-and-nio ...
- 新手前端笔记之--初识html标签
接触前端(好大气的名字啊)已经一个多月了,看了很多视频和博客,有了一定的感性认识,但还是需要总结一下以便系统化所学习的知识,就从html标签开始吧.关于标签,谈论最多的就是简洁和语义化.简洁指html ...
- 原生JavaScript 封装ajax
原生JavaScript 封装ajax function myajax(options){ //新建一个局部对象 用来存放用户输入的各种参数 var opt={ type:options.type ...
- 处理async void 方法中无法捕捉异常信息
利用 NuGet库 Nito.AsyncEx 中的 AsyncContext类. 添加NuGet类库,使用AsyncContext AsyncContext.Run(Action action);
- 栅格数据AE
转自原文 栅格数据AE 两个星期以来一直与栅格数据打交道,对AO的栅格部分应该有了一定的理解,下面是自己的一点体会,希望高手指教:-) 1.栅格数据的存储类型 栅格数据一般可以存储为ESRI GRID ...
- A题之拼音转数字
输入是一个仅仅包括拼音的字符串,请输出相应的数字序列.转换关系例如以下: 描写叙述: 拼音 yi er san si wu liu qi ba jiu 阿拉伯数字 1 2 3 4 5 6 ...
- 关于hive里安装mysql出现错误,如何删除指定的主机或用户?(解决Access denied)
前期博客 你可以按照我写的这篇博客去,按照hive的mysql. 1 复习ha相关 + weekend110的hive的元数据库mysql方式安装配置(完全正确配法)(CentOS版本)(包含卸载系统 ...
- 1.8 Python基础知识 - 数值类型
一.int类型(任意精度整数) 整型类型(int)是表示整数的数据类型.与其他计算机语言有精度限制不同,Python的整数位数可以为任意长度位数(只受限制于计算机内存) 数字字符串即整型常量. pyt ...
- vue.js的基础与语法
Vue的实例 创建第一个实例: {{}} 被称之为插值表达式.可以用来进行文本插值. <!DOCTYPE html> <html lang="en"> &l ...
- .net core 修改网站启动端口
原文:.net core 修改网站启动端口 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/yenange/article/details/81675 ...