前几天在郭霖大神的博客上看了自定义View的知识,感觉受益良多,大神毕竟大神。在此总结一下关于Android 自定义View的用法:
首先,自定义View可以由基本控件或者组合控件组合而成,下面以一个例子来看。创建一个新的Android项目: 新建一个title.xml的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/titleView"
android:background="#A2D1F0"
android:orientation="horizontal" >
<Button
android:id="@+id/titleBackButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回"
android:textColor="#121256"
android:background="#652342"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:text="标题栏"/>
<Button
android:id="@+id/titleOtherButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作"
android:background="#AADD55"/>
</LinearLayout>
布局文件比较简单,一个横向线性布局,包含两个Button 和一个TextView,接下来是对这个组合控件的事件处理: 新建一个TitleLayout.java类文件用于对组合控件进行事件处理:
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class TitleLayout extends LinearLayout {
Button button = null;
TextView textView = null;
public TitleLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
button = (Button) findViewById(R.id.titleBackButton);
button.setOnClickListener(listener);
button = (Button) findViewById(R.id.titleOtherButton);
button.setOnClickListener(listener);
}
private View.OnClickListener listener = new View.OnClickListener()
{
@Override
public void onClick(View v) {
switch(v.getId())
{
case R.id.titleBackButton:
showToast((Activity)getContext(), "你点击了返回按钮");
((Activity) getContext()).finish();
break;
case R.id.titleOtherButton:
showToast(((Activity) getContext()), "你点击了操作按钮");
break;
}
}
};
private void showToast(Context context, String str)
{
Toast.makeText(context, str, Toast.LENGTH_SHORT).show();
}
}
比较简单的事件处理:对两个按钮的单击事件进行处理,单击“返回”键的时候提示并结束当前Activity,单击“操作”按钮的时候进行提示。 那么在接下来就可以在布局文件中使用这个组合控件了: acitivity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
android:id="@+id/main_layout"
tools:context=".MainActivity" >
<com.example.defineView.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" /> <!-- 加入自定义的控件 -->
</LinearLayout>
布局文件也比较简单,值得注意的是使用自定义控件要写完整的类的路径名,当然,也可以使用 <include 布局文件名>
在加载layout文件夹中的布局文件,这样的话就得在activity_main.xml对应的类文件中书写加载的布局文件的事件处理逻辑,否则这个加载的布局文件就没有事件处理效果了
最后是MaiActivity.java文件:
import android.os.Bundle;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
LinearLayout mainLayout = null;
PaintView paintView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 设置窗口为无标题栏模式
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Ok,来看一下效果:
这就是自定义View的第一种,组合控件的简单用法,接下来是通过继承已有控件或者布局并且加上新的逻辑来实现自己需要的功能: 新建一个布局文件through_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/throughView"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"/>
</LinearLayout>
一个简单的竖向线性布局,其中有一个ImageView。 接下来是对应的事件处理文件ThroughView.java:
import android.animation.RectEvaluator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.*;
import android.view.GestureDetector.OnGestureListener;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
public class ThroughView extends FrameLayout implements View.OnTouchListener, OnGestureListener{
GestureDetector myDetector = new GestureDetector(getContext(), this); // 新建一个手势探测器对象用于识别手势
View throughView = null;
private int flag = 1;
public ThroughView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.combination_view, this); // 加载布局文件
setOnTouchListener(this); // 设置触摸事件对象为这个类本身
throughView = findViewById(R.id.throughView);
}
@Override
public boolean onTouch(View arg0, MotionEvent event) { // 实现View.OnTouchListener接口的抽象方法:onTouch
// TODO 自动生成的方法存根
myDetector.onTouchEvent(event); // 触发手势探测器的触摸事件方法
return true; // 注意这里要返回true否则不会继续探测手指在屏幕的移动
}
@Override
public boolean onDown(MotionEvent arg0) {
// TODO 自动生成的方法存根
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float vX,
float vY) {
/*
* e1:手指在屏幕触摸的开始位置,
* e2:手指离开屏幕的结束位置,
* vX:手指在x轴方向的移动速度
* vY:手指在y轴方向的移动速度
*/
if(Math.abs(vX) - Math.abs(vY) > 0) // 这里是设定手指在x轴方向的移动速度大于y方向上的移动速度
{
Toast.makeText(getContext(), "滑动手势", Toast.LENGTH_SHORT).show();
if(flag == 1) // flag 变量用于防止重复响应手势事件
{
flag = 0;
// 新建一个FrameLayout.LayoutParams对象用于指定控件的大小和在父容器中的相对位置
final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.RIGHT; // 相对于父容器右对齐
final Button button = new Button(throughView.getContext());
button.setBackgroundColor(Color.RED);
button.setText("删除");
ThroughView.this.addView(button, lp); // 控件中加入按钮和按钮参数
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
throughView.setVisibility(GONE); // 设置控件的可见性为不占用位置且不可见
final Button b = new Button(ThroughView.this.getContext());
b.setText("恢复");
b.setBackgroundColor(Color.GREEN);
b.setLayoutParams(lp);
ThroughView.this.addView(b); // 控件中加入按钮和按钮参数
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
throughView.setVisibility(VISIBLE); // 设置控件可见
b.setVisibility(GONE);
flag = 1;
}
});
button.setVisibility(GONE);
}
});
}
}
return true;
}
@Override
public void onLongPress(MotionEvent arg0) {
// TODO 自动生成的方法存根
}
@Override
public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2,
float arg3) {
// TODO 自动生成的方法存根
return false;
}
@Override
public void onShowPress(MotionEvent arg0) {
// TODO 自动生成的方法存根
}
@Override
public boolean onSingleTapUp(MotionEvent arg0) {
// TODO 自动生成的方法存根
return false;
}
}
上面代码首先是继承了一个帧布局(FrameLayout),之后实现了View.OnTouchListener和GestureDetector.OnGestureListener接口并实现了他们的抽象方法,在onFling方法加入我们的逻辑。 别忘了在主布局文件activity_main.xml中加入这个控件:
<com.example.defineView.ThroughView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
运行结果如下:
手指在第二行左右滑动:
单击删除按钮:
单击恢复按钮:
Ok,我们继承了FrameLayout并且为这个布局加了一个左右手势识别功能。 这里我们来看一下关于setVisibility方法的参数:
可见(visible),占用布局空间且可见
XML文件:android:visibility="visible"
Java代码:view.setVisibility(View.VISIBLE);
不可见(invisible),占用布局空间但是不可见
XML文件:android:visibility="invisible"
Java代码:view.setVisibility(View.INVISIBLE);
隐藏(GONE),不占用布局空间,也不可见
XML文件:android:visibility="gone"
Java代码:view.setVisibility(View.GONE);
最后呢,就是自绘View来实现自定义View: 新建一个类PaintView.java:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.*;
import android.widget.Toast;
public class PaintView extends android.view.View implements View.OnClickListener{
private Paint paint = new Paint();
private boolean judgeColor = true;
private int count = 0;
public PaintView(Context context) // 这个构造函数使得控件可以在java代码中新建对象
{
super(context);
this.setOnClickListener(this);
paint.setColor(Color.DKGRAY);
}
public PaintView(Context context, AttributeSet attrs) // 这个构造函数使得空间可以可以在布局问件中使用
{
super(context, attrs);
this.setOnClickListener(this);
paint.setColor(Color.DKGRAY);
}
@Override
public void onDraw(Canvas canvas)
{
canvas.drawCircle(50, 50, 50, paint); // 画一个半径为50的圆
paint.setColor(Color.RED);
paint.setTextSize(40);
String str = String.format("%d", count);
canvas.drawText(str, 50 - str.length()*paint.getTextSize() / 4,
50 + paint.getTextSize() / 4, paint); // 在控件中画文字
}
@Override
public void onClick(View v)
{
if(judgeColor)
{
paint.setColor(Color.YELLOW);
}
else
{
paint.setColor(Color.DKGRAY);
}
judgeColor = !judgeColor;
count++;
invalidate(); // 重绘
}
}
在PaintView.java代码中PaintView继承了View这个类并且重写了其onDraw方法来实现自己的控件样式,控件中画了一个圆并且通过它的点击事件来实现改变控件颜色和计数, 最后在主布局文件activity_main.xml中加入这个控件,你也可以在MainActivity.java代码中新建一个再通过代码加入布局文件中:
<com.example.defineView.PaintView
android:layout_width="100dp"
android:layout_height="100dp" />
运行结果:
Yes,运行结果和我们预想的一样,大功告成。 总结一下Android自定义View的三种方法: 1、通过组合已有控件作为新的控件来实现自定义View; 2、继承已有的控件并为其增加新的功能来实现自定义View; 3、通过自绘View并加入事件处理来实现自定义View。**