前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android自定义View

Android自定义View

作者头像
指点
发布2019-01-18 17:33:25
5710
发布2019-01-18 17:33:25
举报
文章被收录于专栏:指点的专栏指点的专栏

前几天在郭霖大神的博客上看了自定义View的知识,感觉受益良多,大神毕竟大神。在此总结一下关于Android 自定义View的用法:

首先,自定义View可以由基本控件或者组合控件组合而成,下面以一个例子来看。创建一个新的Android项目: 新建一个title.xml的布局文件:

代码语言:javascript
复制
<?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类文件用于对组合控件进行事件处理:

代码语言:javascript
复制
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:

代码语言:javascript
复制
<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文件:

代码语言:javascript
复制
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:

代码语言:javascript
复制
<?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:

代码语言:javascript
复制
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中加入这个控件:

代码语言:javascript
复制
<com.example.defineView.ThroughView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

运行结果如下:

这里写图片描述
这里写图片描述

手指在第二行左右滑动:

这里写图片描述
这里写图片描述

单击删除按钮:

这里写图片描述
这里写图片描述

单击恢复按钮:

这里写图片描述
这里写图片描述

Ok,我们继承了FrameLayout并且为这个布局加了一个左右手势识别功能。 这里我们来看一下关于setVisibility方法的参数:

代码语言:javascript
复制
可见(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:

代码语言:javascript
复制
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代码中新建一个再通过代码加入布局文件中:

代码语言:javascript
复制
<com.example.defineView.PaintView 
        android:layout_width="100dp"
        android:layout_height="100dp" />

运行结果:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

Yes,运行结果和我们预想的一样,大功告成。 总结一下Android自定义View的三种方法: 1、通过组合已有控件作为新的控件来实现自定义View; 2、继承已有的控件并为其增加新的功能来实现自定义View; 3、通过自绘View并加入事件处理来实现自定义View。**

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年01月28日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档