自定义控件_自定义控件问题 - CSDN
精华内容
参与话题
  • 自定义控件——完全自定义控件

    千人学习 2018-10-22 21:38:08
    剔除繁杂的理论,注重实践,深入浅出讲解Android中的自定义控件中的完全自定义控件的方法
  • Android自定义控件(一)

    千次阅读 2019-03-01 13:11:22
    Android中提供的控件基本可以满足我们日常开发的需求,但是有些需求只凭这里控件的组合并不能满足,我们可以通过写自定义控件来实现各种功能的控件,比如QQ消息右上角红色提示框水波式消失效果。本篇将对Android...

    Android中提供的控件基本可以满足我们日常开发的需求,但是有些需求只凭这里控件的组合并不能满足,我们可以通过写自定义控件来实现各种功能的控件,比如QQ消息右上角红色提示框水波式消失效果。本篇将对Android自定义控件进行基本的讲解,后面还会持续更新。

    自定义控件要求:
         1. 应当遵守Android标准的规范(命名,可配置,事件处理等)。
         2. 在XML布局中可配置控件的属性。
         3. 对交互应当有合适的反馈,比如按下,点击等。
         4. 具有兼容性, Android版本很多,应该具有广泛的适用性。

    自定义控件学习步骤:
      1 .View的工作原理 
      2 .编写View类 
      3.为View类增加属性 
      4 .绘制屏幕 
      5. 响应用户消息 
      6 .自定义回调函数

    自定义控件两种方式:
      1. 继承ViewGroup 

          例如:ViewGroup、LinearLayout、FrameLayout、RelativeLayout等。

      2. 继承View

          例如:View、TextView、ImageView、Button等。

    自定义控件基本绘制原理:
    View的绘制基本上由measure()、layout()、draw()这个三个函数完成

    1.)测量-Measure过程是计算视图大小,View measure过程相关方法主要有三个:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec)  
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  

    measure调用onMeasure,onMeasure测量宽度、高度然后调用setMeasureDimension保存测量结果,measure,setMeasureDimension是final类型,view的子类不需要重写,onMeasure在view的子类中重写。

    关于MeasureSpec:

    (1) UPSPECIFIED :未指定模式,父容器对于子容器没有任何限制,子容器想要多大就多大,通常在绘制自定义View时才会用

    (2) EXACTLY:精确值模式,父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间,当控件的layout_width和layout_height属性指定为具体数值或match_parent时为该模式。

    (3) AT_MOST:最大值模式,子容器可以是声明大小内的任意大小,当空间的宽高设置为wrap_content时为该模式

    2.)布局-Layout过程用于设置视图在屏幕中显示的位置,View layout过程相关方法主要要三个:

    public void layout(int l, int t, int r, int b)
    protected boolean setFrame(int left, int top, int right, int bottom)
    protected void onLayout(boolean changed, int left, int top, int right, int bottom)

    layout通过调用setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,onLayout一般只会在自定义ViewGroup中才会使用

    3.)绘制-draw过程主要用于利用前两步得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。

    public void draw(Canvas canvas)
    protected void onDraw(Canvas canvas)

    通过调用draw函数进行视图绘制,在View类中onDraw函数是个空函数,最终的绘制需求需要在自定义的onDraw函数中进行实现,比如ImageView完成图片的绘制,如果自定义ViewGroup这个函数则不需要重载。

    上面对自定义View的流程作了基本的介绍,参考下面两篇博客

    https://www.cnblogs.com/Free-Thinker/p/6113461.html

    http://www.cnblogs.com/whoislcj/p/5708778.html

     

    下面是一个绘制简单百分比的例子

    定义一个继承View的类,重写构造方法,onMeasure,onDraw方法,即可构建一个简单的自定义控件

    SimpleView类代码如下:
     

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.RectF;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
     
    public class SimpleView extends View {
     
        private final static String TAG = SimpleView.class.getSimpleName();
        //画笔
        private Paint mPaint;
        private RectF oval;
     
        public SimpleView(Context context) {
            super(context);
            init();
        }
     
        public SimpleView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
     
        public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
     
        private void init(){
            mPaint = new Paint();
            //设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
            mPaint.setAntiAlias(true);
            mPaint.setTextSize(30.0f);
            oval=new RectF();
        }
     
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            Log.e(TAG, "onMeasure--widthMode-->" + widthMode);
            switch (widthMode) {
                case MeasureSpec.EXACTLY:
                    //精确值模式,当控件的layout_width和layout_height属性指定为具体数值或match_parent时。
                    break;
                case MeasureSpec.AT_MOST:
                    //最大值模式,当空间的宽高设置为wrap_content时。
                    break;
                case MeasureSpec.UNSPECIFIED:
                    //未指定模式,View想多大就多大,通常在绘制自定义View时才会用。
                    break;
            }
            //取最小边为控件的宽高的最小值
            int minWidth=widthSize>heightSize?heightSize:widthSize;
            setMeasuredDimension(minWidth,minWidth);
        }
     
     
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint.setColor(Color.GRAY);
            // FILL填充, STROKE描边,FILL_AND_STROKE填充和描边
            mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            int with = getWidth();
            int height = getHeight();
            Log.e(TAG, "onDraw---->" + with + "*" + height);
            float radius = with / 4;
            canvas.drawCircle(with / 2, with / 2, radius, mPaint);
            mPaint.setColor(Color.RED);
            oval.set(with / 2 - radius, with / 2 - radius, with / 2
                    + radius, with / 2 + radius);//用于定义的圆弧的形状和大小的界限
            int sweepAngle=120;
            canvas.drawArc(oval, 0, -sweepAngle, true, mPaint);  //根据进度画圆弧
            double percent=sweepAngle/360.0;
            //设置文本颜色
            mPaint.setColor(Color.WHITE);
            //绘制文本百分比数据
            canvas.drawText(String.format("%.2f",percent)+"%",(float)(with/2+radius*Math.cos(sweepAngle*Math.PI/360)/4) 
                    ,(float)(with/2-radius*Math.sin(sweepAngle*Math.PI/360)/3),mPaint);
            canvas.drawText(String.format("%.2f",1-percent)+"%",(float)(with/2-radius*Math.cos(sweepAngle*Math.PI/360)) 
                    ,(float)(with/2+radius*Math.sin(sweepAngle*Math.PI/360)/3),mPaint);
        }
    }

    展开全文
  • C# 自定义用户控件 此处为转载文章,用于记录自我学习过程,原文链接地址http://blog.csdn.net/xiongxuanwen/article/details/2605109 上篇:控件制作 本例是制作一个简单的自定义控件,然后用一个简单的测试...
    C# 自定义用户控件

     此处为转载文章,用于记录自我学习过程,原文链接地址http://blog.csdn.net/xiongxuanwen/article/details/2605109 

    上篇:控件制作
     
    本例是制作一个简单的自定义控件,然后用一个简单的测试程序,对于初学者来说,本例子比较简单,只能起到抛石引玉的效果。
    我也是在学习当中,今后会将自己所学的逐步写出来和大家交流共享。
     
    第一步:新建一个控件库项目:myControl
     
    第二步:从工具箱里面拖动1个PictureBox、1个Button、6个Lable控件到用户界面上,布局如下:
           如上图,设置pictureBox的Name为picBox,背景为白色,Button的Name为btnOpen,另外靠左的三个Lable的Text属性分别为:文件名称,文件大小,文件尺寸,靠右的三个Lable的Name分别为:lblName, lblLength, lblSize.
     
    第三步:添加处理程序代码
    在btnOpen的Click事件写入代码,打开一个打开文件对话框,选择一个图形文件,打开并将它显示在picBox上。
     
    复制代码
    private void btnOpen_Click(object sender, EventArgs e)
    {
    OpenFileDialog ofdPic = new OpenFileDialog();
    ofdPic.Filter = "JPG(*.JPG;*.JPEG);gif文件(*.GIF)|*.jpg;*.jpeg;*.gif";
    ofdPic.FilterIndex = 1;
    ofdPic.RestoreDirectory = true;
    ofdPic.FileName = "";
    if (ofdPic.ShowDialog() == DialogResult.OK)
    {
    string sPicPaht = ofdPic.FileName.ToString();
    FileInfo fiPicInfo = new FileInfo(sPicPaht);
    long lPicLong = fiPicInfo.Length / 1024;
    string sPicName = fiPicInfo.Name;
    string sPicDirectory = fiPicInfo.Directory.ToString();
    string sPicDirectoryPath = fiPicInfo.DirectoryName;
    Bitmap bmPic = new Bitmap(sPicPaht);
    if (lPicLong > 400)
    {
    MessageBox.Show("此文件大小為" + lPicLong + "K;已超過最大限制的K范圍!");
    }
    else
    {
    Point ptLoction = new Point(bmPic.Size);
    if (ptLoction.X > picBox.Size.Width || ptLoction.Y > picBox.Size.Height)
    {
    picBox.SizeMode = PictureBoxSizeMode.Zoom;
    }
    else
    {
    picBox.SizeMode = PictureBoxSizeMode.CenterImage;
    }
    }
    picBox.LoadAsync(sPicPaht);
    lblName.Text = sPicName;
    lblLength.Text = lPicLong.ToString() + " KB";
    lblSize.Text = bmPic.Size.Width.ToString() + "×" + bmPic.Size.Height.ToString();
    }
    }
    复制代码
     
    第四步:测试控件
    按F5启动调试,弹出如下窗体:
    单击“打开”按钮,弹出打开文件对话框:
    选择一张图片,单击“打开”,可以看到在picBox上显示了打开的图片:
    第五步:查看成生的控件文件,到该项目文件目录下的bin->debug中可找到。
     
    下篇:控件测试
    第一步:新建一个C# Windows 应用程序,名为TestMyButton.
     
    第二步:增加自定义的用户控件
    右键单击工具箱中任意一个控件,弹出右键菜单如下:
    单击“选择项”,弹出如下对话框:
    单击“浏览”,弹出打开对话框:
    选中控件文件 mybutton.dll ,单击“打开”按钮,回到自定义工具箱,系统会默认把你刚才选中的控件打上 勾。
    返回vs编辑器,可看到工具箱中多出了一个UserControl:
     
    第三步:拖动1个自定义的控件到测试窗口
    第四步 测试程序
    单击“打开”按钮:
    选择一个图片,打开,显示该图:
    测试成功
    展开全文
  • 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了...现在前半部分的承诺已经如约兑现了,那么今天我就要来兑现后面部分的承诺,讲一讲自定义View的实现方法,同时这也是带

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967


    不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了LayoutInflater的原理分析、视图的绘制流程、视图的状态及重绘等知识,算是把View中很多重要的知识点都涉及到了。如果你还没有看过我前面的几篇文章,建议先去阅读一下,多了解一些原理方面的东西。


    之前我有承诺过,会在View这个话题上多写几篇博客,讲一讲View的工作原理,以及自定义View的方法。现在前半部分的承诺已经如约兑现了,那么今天我就要来兑现后面部分的承诺,讲一讲自定义View的实现方法,同时这也是带你一步步深入了解View系列的完结篇。


    一些接触Android不久的朋友对自定义View都有一丝畏惧感,总感觉这是一个比较高级的技术,但其实自定义View并不复杂,有时候只需要简单几行代码就可以完成了。


    如果说要按类型来划分的话,自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。那么下面我们就来依次学习一下,每种方式分别是如何自定义View的。


    一、自绘控件


    自绘控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的,而这部分内容我们已经在 Android视图绘制流程完全解析,带你一步步深入了解View(二) 中学习过了。


    下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次。新建一个CounterView继承自View,代码如下所示:

    public class CounterView extends View implements OnClickListener {
    
    	private Paint mPaint;
    	
    	private Rect mBounds;
    
    	private int mCount;
    	
    	public CounterView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    		mBounds = new Rect();
    		setOnClickListener(this);
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    		mPaint.setColor(Color.BLUE);
    		canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    		mPaint.setColor(Color.YELLOW);
    		mPaint.setTextSize(30);
    		String text = String.valueOf(mCount);
    		mPaint.getTextBounds(text, 0, text.length(), mBounds);
    		float textWidth = mBounds.width();
    		float textHeight = mBounds.height();
    		canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
    				+ textHeight / 2, mPaint);
    	}
    
    	@Override
    	public void onClick(View v) {
    		mCount++;
    		invalidate();
    	}
    
    }

    可以看到,首先我们在CounterView的构造函数中初始化了一些数据,并给这个View的本身注册了点击事件,这样当CounterView被点击的时候,onClick()方法就会得到调用。而onClick()方法中的逻辑就更加简单了,只是对mCount这个计数器加1,然后调用invalidate()方法。通过 Android视图状态及重绘流程分析,带你一步步深入了解View(三) 这篇文章的学习我们都已经知道,调用invalidate()方法会导致视图进行重绘,因此onDraw()方法在稍后就将会得到调用。


    既然CounterView是一个自绘视图,那么最主要的逻辑当然就是写在onDraw()方法里的了,下面我们就来仔细看一下。这里首先是将Paint画笔设置为蓝色,然后调用Canvas的drawRect()方法绘制了一个矩形,这个矩形也就可以当作是CounterView的背景图吧。接着将画笔设置为黄色,准备在背景上面绘制当前的计数,注意这里先是调用了getTextBounds()方法来获取到文字的宽度和高度,然后调用了drawText()方法去进行绘制就可以了。


    这样,一个自定义的View就已经完成了,并且目前这个CounterView是具备自动计数功能的。那么剩下的问题就是如何让这个View在界面上显示出来了,其实这也非常简单,我们只需要像使用普通的控件一样来使用CounterView就可以了。比如在布局文件中加入如下代码:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.example.customview.CounterView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_centerInParent="true" />
    
    </RelativeLayout>

    可以看到,这里我们将CounterView放入了一个RelativeLayout中,然后可以像使用普通控件来给CounterView指定各种属性,比如通过layout_width和layout_height来指定CounterView的宽高,通过android:layout_centerInParent来指定它在布局里居中显示。只不过需要注意,自定义的View在使用的时候一定要写出完整的包名,不然系统将无法找到这个View。


    好了,就是这么简单,接下来我们可以运行一下程序,并不停地点击CounterView,效果如下图所示。




    怎么样?是不是感觉自定义View也并不是什么高级的技术,简单几行代码就可以实现了。当然了,这个CounterView功能非常简陋,只有一个计数功能,因此只需几行代码就足够了,当你需要绘制比较复杂的View时,还是需要很多技巧的。


    二、组合控件


    组合控件的意思就是,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。


    举个例子来说,标题栏就是个很常见的组合控件,很多界面的头部都会放置一个标题栏,标题栏上会有个返回按钮和标题,点击按钮后就可以返回到上一个界面。那么下面我们就来尝试去实现这样一个标题栏控件。


    新建一个title.xml布局文件,代码如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#ffcb05" >
    
        <Button
            android:id="@+id/button_left"
            android:layout_width="60dp"
            android:layout_height="40dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="5dp"
            android:background="@drawable/back_button"
            android:text="Back"
            android:textColor="#fff" />
    
        <TextView
            android:id="@+id/title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="This is Title"
            android:textColor="#fff"
            android:textSize="20sp" />
    
    </RelativeLayout>

    在这个布局文件中,我们首先定义了一个RelativeLayout作为背景布局,然后在这个布局里定义了一个Button和一个TextView,Button就是标题栏中的返回按钮,TextView就是标题栏中的显示的文字。


    接下来创建一个TitleView继承自FrameLayout,代码如下所示:

    public class TitleView extends FrameLayout {
    
    	private Button leftButton;
    
    	private TextView titleText;
    
    	public TitleView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		LayoutInflater.from(context).inflate(R.layout.title, this);
    		titleText = (TextView) findViewById(R.id.title_text);
    		leftButton = (Button) findViewById(R.id.button_left);
    		leftButton.setOnClickListener(new OnClickListener() {
    			@Override
    			public void onClick(View v) {
    				((Activity) getContext()).finish();
    			}
    		});
    	}
    
    	public void setTitleText(String text) {
    		titleText.setText(text);
    	}
    
    	public void setLeftButtonText(String text) {
    		leftButton.setText(text);
    	}
    
    	public void setLeftButtonListener(OnClickListener l) {
    		leftButton.setOnClickListener(l);
    	}
    
    }

    TitleView中的代码非常简单,在TitleView的构建方法中,我们调用了LayoutInflater的inflate()方法来加载刚刚定义的title.xml布局,这部分内容我们已经在 Android LayoutInflater原理分析,带你一步步深入了解View(一) 这篇文章中学习过了。


    接下来调用findViewById()方法获取到了返回按钮的实例,然后在它的onClick事件中调用finish()方法来关闭当前的Activity,也就相当于实现返回功能了。


    另外,为了让TitleView有更强地扩展性,我们还提供了setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法,分别用于设置标题栏上的文字、返回按钮上的文字、以及返回按钮的点击事件。


    到了这里,一个自定义的标题栏就完成了,那么下面又到了如何引用这个自定义View的部分,其实方法基本都是相同的,在布局文件中添加如下代码:

    <RelativeLayout 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" >
    
        <com.example.customview.TitleView
            android:id="@+id/title_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
        </com.example.customview.TitleView>
    
    </RelativeLayout>

    这样就成功将一个标题栏控件引入到布局文件中了,运行一下程序,效果如下图所示:




    现在点击一下Back按钮,就可以关闭当前的Activity了。如果你想要修改标题栏上显示的内容,或者返回按钮的默认事件,只需要在Activity中通过findViewById()方法得到TitleView的实例,然后调用setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法进行设置就OK了。


    三、继承控件


    继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能,比如 Android PowerImageView实现,可以播放动画的强大ImageView 这篇文章中介绍的PowerImageView就是一个典型的继承控件。


    为了能够加深大家对这种自定义View方式的理解,下面我们再来编写一个新的继承控件。ListView相信每一个Android程序员都一定使用过,这次我们准备对ListView进行扩展,加入在ListView上滑动就可以显示出一个删除按钮,点击按钮就会删除相应数据的功能。


    首先需要准备一个删除按钮的布局,新建delete_button.xml文件,代码如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <Button xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/delete_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/delete_button" >
    
    </Button>

    这个布局文件很简单,只有一个按钮而已,并且我们给这个按钮指定了一张删除背景图。


    接着创建MyListView继承自ListView,这就是我们自定义的View了,代码如下所示:

    public class MyListView extends ListView implements OnTouchListener,
    		OnGestureListener {
    
    	private GestureDetector gestureDetector;
    
    	private OnDeleteListener listener;
    
    	private View deleteButton;
    
    	private ViewGroup itemLayout;
    
    	private int selectedItem;
    
    	private boolean isDeleteShown;
    
    	public MyListView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		gestureDetector = new GestureDetector(getContext(), this);
    		setOnTouchListener(this);
    	}
    
    	public void setOnDeleteListener(OnDeleteListener l) {
    		listener = l;
    	}
    
    	@Override
    	public boolean onTouch(View v, MotionEvent event) {
    		if (isDeleteShown) {
    			itemLayout.removeView(deleteButton);
    			deleteButton = null;
    			isDeleteShown = false;
    			return false;
    		} else {
    			return gestureDetector.onTouchEvent(event);
    		}
    	}
    
    	@Override
    	public boolean onDown(MotionEvent e) {
    		if (!isDeleteShown) {
    			selectedItem = pointToPosition((int) e.getX(), (int) e.getY());
    		}
    		return false;
    	}
    
    	@Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    			float velocityY) {
    		if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) {
    			deleteButton = LayoutInflater.from(getContext()).inflate(
    					R.layout.delete_button, null);
    			deleteButton.setOnClickListener(new OnClickListener() {
    				@Override
    				public void onClick(View v) {
    					itemLayout.removeView(deleteButton);
    					deleteButton = null;
    					isDeleteShown = false;
    					listener.onDelete(selectedItem);
    				}
    			});
    			itemLayout = (ViewGroup) getChildAt(selectedItem
    					- getFirstVisiblePosition());
    			RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
    					LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    			params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
    			params.addRule(RelativeLayout.CENTER_VERTICAL);
    			itemLayout.addView(deleteButton, params);
    			isDeleteShown = true;
    		}
    		return false;
    	}
    
    	@Override
    	public boolean onSingleTapUp(MotionEvent e) {
    		return false;
    	}
    
    	@Override
    	public void onShowPress(MotionEvent e) {
    
    	}
    
    	@Override
    	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
    			float distanceY) {
    		return false;
    	}
    
    	@Override
    	public void onLongPress(MotionEvent e) {
    	}
    	
    	public interface OnDeleteListener {
    
    		void onDelete(int index);
    
    	}
    
    }

    由于代码逻辑比较简单,我就没有加注释。这里在MyListView的构造方法中创建了一个GestureDetector的实例用于监听手势,然后给MyListView注册了touch监听事件。然后在onTouch()方法中进行判断,如果删除按钮已经显示了,就将它移除掉,如果删除按钮没有显示,就使用GestureDetector来处理当前手势。


    当手指按下时,会调用OnGestureListener的onDown()方法,在这里通过pointToPosition()方法来判断出当前选中的是ListView的哪一行。当手指快速滑动时,会调用onFling()方法,在这里会去加载delete_button.xml这个布局,然后将删除按钮添加到当前选中的那一行item上。注意,我们还给删除按钮添加了一个点击事件,当点击了删除按钮时就会回调onDeleteListener的onDelete()方法,在回调方法中应该去处理具体的删除操作。


    好了,自定义View的功能到此就完成了,接下来我们需要看一下如何才能使用这个自定义View。首先需要创建一个ListView子项的布局文件,新建my_list_view_item.xml,代码如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:descendantFocusability="blocksDescendants"
        android:orientation="vertical" >
    
        <TextView
            android:id="@+id/text_view"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:layout_centerVertical="true"
            android:gravity="left|center_vertical"
            android:textColor="#000" />
    
    </RelativeLayout>
    然后创建一个适配器MyAdapter,在这个适配器中去加载my_list_view_item布局,代码如下所示:
    public class MyAdapter extends ArrayAdapter<String> {
    
    	public MyAdapter(Context context, int textViewResourceId, List<String> objects) {
    		super(context, textViewResourceId, objects);
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		View view;
    		if (convertView == null) {
    			view = LayoutInflater.from(getContext()).inflate(R.layout.my_list_view_item, null);
    		} else {
    			view = convertView;
    		}
    		TextView textView = (TextView) view.findViewById(R.id.text_view);
    		textView.setText(getItem(position));
    		return view;
    	}
    
    }
    到这里就基本已经完工了,下面在程序的主布局文件里面引入MyListView这个控件,如下所示:
    <RelativeLayout 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" >
    
        <com.example.customview.MyListView
            android:id="@+id/my_list_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
        </com.example.customview.MyListView>
    
    </RelativeLayout>
    最后在Activity中初始化MyListView中的数据,并处理了onDelete()方法的删除逻辑,代码如下所示:
    public class MainActivity extends Activity {
    
    	private MyListView myListView;
    
    	private MyAdapter adapter;
    
    	private List<String> contentList = new ArrayList<String>();
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		requestWindowFeature(Window.FEATURE_NO_TITLE);
    		setContentView(R.layout.activity_main);
    		initList();
    		myListView = (MyListView) findViewById(R.id.my_list_view);
    		myListView.setOnDeleteListener(new OnDeleteListener() {
    			@Override
    			public void onDelete(int index) {
    				contentList.remove(index);
    				adapter.notifyDataSetChanged();
    			}
    		});
    		adapter = new MyAdapter(this, 0, contentList);
    		myListView.setAdapter(adapter);
    	}
    
    	private void initList() {
    		contentList.add("Content Item 1");
    		contentList.add("Content Item 2");
    		contentList.add("Content Item 3");
    		contentList.add("Content Item 4");
    		contentList.add("Content Item 5");
    		contentList.add("Content Item 6");
    		contentList.add("Content Item 7");
    		contentList.add("Content Item 8");
    		contentList.add("Content Item 9");
    		contentList.add("Content Item 10");
    		contentList.add("Content Item 11");
    		contentList.add("Content Item 12");
    		contentList.add("Content Item 13");
    		contentList.add("Content Item 14");
    		contentList.add("Content Item 15");
    		contentList.add("Content Item 16");
    		contentList.add("Content Item 17");
    		contentList.add("Content Item 18");
    		contentList.add("Content Item 19");
    		contentList.add("Content Item 20");
    	}
    
    }

    这样就把整个例子的代码都完成了,现在运行一下程序,会看到MyListView可以像ListView一样,正常显示所有的数据,但是当你用手指在MyListView的某一行上快速滑动时,就会有一个删除按钮显示出来,如下图所示:



    点击一下删除按钮就可以将第6行的数据删除了。此时的MyListView不仅保留了ListView原生的所有功能,还增加了一个滑动进行删除的功能,确实是一个不折不扣的继承控件。

    到了这里,我们就把自定义View的几种实现方法全部讲完了,虽然每个例子都很简单,但是万变不离其宗,复杂的View也是由这些简单的原理堆积出来的。经过了四篇文章的学习,相信每个人对View的理解都已经较为深入了,那么带你一步步深入了解View系列的文章就到此结束,感谢大家有耐心看到最后。

    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

            

    展开全文
  • C# 自定义控件的实现

    千次阅读 2016-02-23 09:13:06
    我们在开发Winform程序的时候,需要经常性的对界面的一些控件进行初始化,或者经常简单的封装,以方便我们在界面设计过程中反复使用。本文主要介绍在我的一些项目中经常性的界面处理操作和代码,以便为大家开发的...

    http://www.cnblogs.com/wuhuacong/p/3968544.html

    Winform开发中常见界面的DevExpress处理操作

    我们在开发Winform程序的时候,需要经常性的对界面的一些控件进行初始化,或者经常简单的封装,以方便我们在界面设计过程中反复使用。本文主要介绍在我的一些项目中经常性的界面处理操作和代码,以便为大家开发的时候提供必要的参考。

    1、选择用户的控件封装操作

    在一些系统模块里面,我们需要选择系统人员作为经办人员的操作,如下面几个界面场景所示。

    我们注意到,一般在我们选择的时候,界面会弹出一个新的层给我们选择,里面通过列表详细展示相关的信息,还可以支持搜索,非常方便。

    当我们完成选择的时候,我们看到界面会只有一个人员名称的显示,不占用额外的地方显示。

    这种界面效果是如何实现的呢?下面进行详细的介绍。

    1)首先我们定义一个自定义控件,让其继承自XtraUserControl 即可。

        /// <summary>
        /// 经办人员、操作人员的选择控件封装
        /// </summary>
        public partial class OperatorSelectControl : XtraUserControl
        {

    2)然后在DevExpress的界面工具箱上拖动一个SearchLookUpEdit 控件到我们新的用户控件OperatorSelectControl 上。

    调整好用户界面控件的排版相关属性,就会得到下面的界面所示。

    3)在设计视图里面,我们为这个SearchLookUpEdit控件的GridView设置它的显示字段,如下所示,每个字段主要绑定FieldName(属性或者字段)和Caption(显示名称)。

    这些必备的处理操作完成后,我们可以通过代码或者设计器把这个控件的显示内容和存储内容进行设定,并绑定它的数据源即可(根据需要调用自己的函数),如下所示。

    复制代码
            private void OperatorSelectControl_Load(object sender, EventArgs e)
            {
                if (!this.DesignMode)
                {
                    txtOperator.Properties.ValueMember = "ID";
                    txtOperator.Properties.DisplayMember = "FullName";
                    txtOperator.Properties.DataSource = SecurityHelper.GetSimpleUsers();
                }
            }
    复制代码

    4)我们为了方便,还可以进一步处理控件的显示内容和返回的值内容,我们希望绑定值或者获取值的时候,使用Text属性就可以了,那么我们重载一下这个自定义控件的Text属性即可。

    复制代码
            public override string Text
            {
                get
                {
                    string result = "";
                    if (this.txtOperator.EditValue != null)
                    {
                        result = this.txtOperator.EditValue.ToString();
                    }
                    return result;
                }
                set
                {
                    this.txtOperator.EditValue = value;
                }
            }
    复制代码

    5)这样控件的操作就完成了,编译代码后,我们在工具箱上就可以看到最新的控件图标了。

    接着我们把相关的控件拖动到需要的地方(如果是界面和控件在相同的工程里面,注意需要移除重复的工程引用),重新编译系统代码,那么本文开始的界面效果就可以出现了。

     

    2、会员卡级别选择操作

    刚才说了,人员选择可以使用这种弹出列表选择的方式,其实很多地方可以用这个方式来进行选择,如一些相对比较少记录的信息就很适合这种显示方式,比如在会员管理里面的,会员卡级别的选择,也可以采用这种方式。

    这个模块的做法也和第一种很类似,有点不同的是,我需要选择后把会员卡的折扣也关联显示出来,那我们应该如何处理呢?

    1)定义控件和事件处理

    为了方便在控件选择后进行事件的触发处理,我在这里定义了一个事件处理器SelectedValueChanged

    复制代码
        /// <summary>
        /// 卡级选择控件
        /// </summary>
        public partial class CardGradeSelectControl : XtraUserControl
        {
            /// <summary>
            /// 选择项发生变化的事件处理
            /// </summary>
            public event EventHandler SelectedValueChanged;
    复制代码

    然后在内部控件的EditValue改变的时候,在其中的事件里面触发我们自定义的事件即可, 如下所示。

    复制代码
            private void txtCardGrade_EditValueChanged(object sender, EventArgs e)
            {
                if (SelectedValueChanged != null)
                {
                    SelectedValueChanged(sender, e);
                }
            }
    复制代码

    2)在窗体界面中自定义控件的事件调用

    在会员编辑界面里面,我们对这个卡级别的选择控件的自定义事件进行处理即可。

    处理事件里面,我们获取对应卡级别的优惠折扣(GetDiscountByGradeNo),然后绑定到界面的控件显示即可,这样就实现了联动效果了。

    复制代码
        public partial class FrmEditMember : BaseEditForm
        {
            /// <summary>
            /// 创建一个临时对象,方便在附件管理中获取存在的GUID
            /// </summary>
            private MemberInfo tempInfo = new MemberInfo();
    
            public FrmEditMember()
            {
                InitializeComponent();
    
                this.txtCardGrade.SelectedValueChanged += new EventHandler(txtCardGrade_SelectedValueChanged);
            }
    
            void txtCardGrade_SelectedValueChanged(object sender, EventArgs e)
            {
                string gradeNo = txtCardGrade.Text;
                if (!string.IsNullOrEmpty(gradeNo))
                {
                    this.txtDiscount.Value = BLLFactory<CardGrade>.Instance.GetDiscountByGradeNo(gradeNo);
                }
            }
    复制代码

     

    3、创建人员和创建时间的显示处理

    在我们窗体数据的编辑界面里面,为了友好显示,我们可能需要显示数据的创建人和创建时间。这种效果是如何实现的,我们来看看。

    1)数据显示时候处理

    数据显示的时候,可能是新增界面的显示,也可以是现有记录的显示,我们需要区分对待。

    如果是已有数据,我们需要解析创建人员的ID,显示已有时间即可;如果是新增界面,那么我们把当前登陆用户的名称显示出来,及显示当前时间即可。

    在设计模式里面,把控件设置为ReadOnly=true,不用编辑修改控件的值即可。

    复制代码
            /// <summary>
            /// 数据显示的函数
            /// </summary>
            public override void DisplayData()
            {
                InitDictItem();//数据字典加载(公用)
    
                if (!string.IsNullOrEmpty(ID))
                {
                    #region 显示信息
                    MemberInfo info = BLLFactory<WHC.Member.BLL.Member>.Instance.FindByID(ID);
                    if (info != null)
                    {
                        tempInfo = info;//重新给临时对象赋值,使之指向存在的记录对象
    
                        txtHandNo.Text = info.HandNo;
                        txtCardNo.Text = info.CardNo;
                        txtCardCode.Text = info.CardCode;
                        txtCardStatus.SetComboBoxItem(info.CardStatus.ToString());
                        txtOpenUser.Text = info.OpenUser;
    ..............
    
                        txtCreator.Text = SecurityHelper.GetFullNameByID(info.Creator);
                        txtCreateTime.SetDateTime(info.CreateTime);
                    }
                    #endregion             
                }
                else
                {this.txtOpenDate.DateTime = DateTime.Now;
                    this.txtHandNo.Text = IDUtils.NewId();
    
                    this.txtCreateTime.DateTime = DateTime.Now;
                    this.txtCreator.Text = LoginUserInfo.FullName;//默认为当前登录用户
                }
    
                this.portraitControl1.MemberID = tempInfo.ID;
                this.portraitControl1.BindPicture(tempInfo.ID);
    
                //tempInfo在对象存在则为指定对象,新建则是全新的对象,但有一些初始化的GUID用于附件上传
                SetAttachInfo(tempInfo);
            }
    复制代码

    但用户保存操作的时候,如果是新增数据,我们需要把当前时间和当前用户的信息记录到数据库里面,因此需要增加一些代码进行数据的关联处理。

    复制代码
            /// <summary>
            /// 新增状态下的数据保存
            /// </summary>
            /// <returns></returns>
            public override bool SaveAddNew()
            {
                MemberInfo info = tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用
                SetInfo(info);
    
                info.Creator = LoginUserInfo.ID.ToString();
                info.CreateTime = DateTime.Now;
                info.Dept_ID = LoginUserInfo.DeptId;
                info.Company_ID = LoginUserInfo.CompanyId;
    
                try
                {
                    #region 新增数据
                    //检查是否还有其他相同关键字的记录
                    bool exist = BLLFactory<WHC.Member.BLL.Member>.Instance.IsExistKey("CardNo", info.CardNo);
                    if (exist)
                    {
                        MessageDxUtil.ShowTips("指定的【会员卡号】已经存在,不能重复添加,请修改");
                        return false;
                    }
    
                    bool succeed = BLLFactory<WHC.Member.BLL.Member>.Instance.Insert(info);
                    if (succeed)
                    {
                        //可添加其他关联操作
    
                        return true;
                    }
                    #endregion
                }
                catch (Exception ex)
                {
                    LogTextHelper.Error(ex);
                    MessageDxUtil.ShowError(ex.Message);
                }
                return false;
            }
    复制代码

    我们为了方便,一般是在数据库存储人员的ID,但是列表显示的时候,我们也就需要把对应的人员ID转换为人员名称了。

                this.winGridViewPager1.gridView1.CustomColumnDisplayText += new DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(gridView1_CustomColumnDisplayText);
    复制代码
            void gridView1_CustomColumnDisplayText(object sender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
            {
                if (e.Column.FieldName == "Operator" || e.Column.FieldName == "Editor" || e.Column.FieldName == "Creator")
                {
                    if (e.Value != null)
                    {
                        e.DisplayText = SecurityHelper.GetFullNameByID(e.Value.ToString());
                    }
                }
            }
    复制代码

    展开全文
  • 自定义控件

    2019-01-12 09:01:40
    o# 自定义控件 # Day01 系统控件回顾 文本控件 TextView和EditText 图片控件 ImageView 按钮控件 Button和ImageButton 进度条 ProgressBar 单选按钮 RadioButton和RadioGroup 复选按钮 CheckBox 状态开关按钮...
  • labview自定义控件

    千次阅读 2018-06-01 09:53:12
    创建自定义输入控件、显示控件自定义类型»目录LabVIEW 2011帮助版本日期:June 2011产品编号:371361H-0118»查看产品信息下载帮助(仅限Windows)自定义输入控件和显示控件是对现有前面板对象集的扩展。...
  • Android自定义控件的三种实现方式

    万次阅读 多人点赞 2017-10-19 16:25:14
    Android 自定义控件三种实现方法为组合原生控件,自己绘制和继承原生控件. 1.组合原生控件 将自己需要的控件组合起来变成一个新控件,如下制作常见的app页面头部.  新建一个Android项目,创建一个头部布局view_top....
  • 自定义控件其实很简单1

    千次阅读 2018-08-20 06:43:55
    自定义View,很多初学Android的童鞋听到这么一句话绝逼是一脸膜拜!因为在很多初学者眼里,能够自己去画一个View绝逼是一件很屌很Cool的事!但是,同样而言,自定义View对初学者来说却往往可望而不可及,可望是因为...
  • C#自定义控件的创建

    万次阅读 2019-06-14 14:19:51
    本人在开发自定义控件时走了一些弯路,写下此篇,希望能够给有需要的朋友一些帮助,也借此加深自己的印象。 1.创建自定义控件 选择【经典桌面】——【窗体控件库】 2.添加控件,组合成一个新的控件 自定...
  • Android 自定义控件之组合控件

    千次阅读 2018-04-18 20:46:39
    一、前言 最近做一个项目又一次用到类似像微信... 这回使用的Android的自定义控件中的组合控件。二、自定义组合控件原理 项目中经常会遇见很多相似或者相同的布局,比如APP的标题栏,通过将这些布局抽取出来,放...
  • 如何自定义控件

    2018-06-21 12:08:31
    【WinForm】创建自定义控件虽然VS为我们提供了很多控件可以使用,但有时候这些控件仍然不能满足我们的要求,比如我们要对部分控件进行一些个性化的定制,例如美化控件,这时候就需要自己绘制控件,或是在原有控件的...
  • C#开发之自定义控件的简单使用

    万次阅读 2012-10-12 10:34:27
    1.开发自定义控件 自定义控件在开发中算常用的技术,因为在解决实际问题时往往.NET Framework提供的控件不能满足所有的需求,还好可以根据.NET Framework提供的基础控件组合扩展成自定义控件。 开发自定义控件需要...
  • 最近想山寨一个下载软件,(下载中)任务列表想用自定义控件来实现,但不知道如何自定义一个任务列表控件(能在列表项中添加link,button,label等控件)。 最好能有C#自定义控件的电子书!谢谢!
  • WinForm创建自定义控件

    万次阅读 2017-05-03 10:34:26
    虽然VS为我们提供了很多控件可以使用,但有时候...自定义控件分为三种  1、组合控件(CompositeControls):在原有控件的基础上根据需要进行组合  2、扩展控件(ExtendedControls):继承自原有控件,添加一些新的
  • 在项目中增加几个用户自定义控件,想及时在窗口Form上添加时却发现工具箱根本就没有这些用户自定义控件,研究了很久,发现有两个解决办法。通过以下两种方法操作后,再重新编译,在工具箱上就会出现用户自定义控件了...
  • Android自定义控件三部曲文章索引

    万次阅读 多人点赞 2019-09-17 19:53:00
    前言:在我从C++转到Android时,就被Android里炫彩斑斓的自定义控件深深折服,想知道如果想利用C++实现这些功能,那是相当困难的。从那时候起,我就想,等我学会了自定义控件,一定要写一篇系列出来,方便后来者能更...
  • 接上文:编写Qt Designer自定义控件(二)——给自定义控件添加属性  控件编写完毕以后,把生成的dll和lib文件一起拷贝到Qt安装目录下的插件目录里,比如我安装在D盘里的Qt 4.8.4,路径为:D:\Qt\4.8.4\plugins\...
  • QTCreator中制作和使用自定义控件

    万次阅读 2014-05-17 14:07:44
     在网上可以看到很多类似的博文介绍自定义控件方法,当然《QT C++ GUI 第二版》书中也有介绍应。我并不是按照这些方法来做的,内部知识是相通的。我用的是Qt5.1版本操作步骤如下: 首先说一下用的是VS编译器,...
  • Qt编写自定义控件大全+designer源码

    万次阅读 2019-04-05 13:25:47
    近期抽空将自定义控件的主界面全部重写了一遍,采用左侧树状节点导航,看起来更精美高大上一点,后期准备单独做个工具专用每个控件的属性设计,其实qt自带的designer就具备这些功能,于是从qt4的源码中抽取出来,...
1 2 3 4 5 ... 20
收藏数 330,524
精华内容 132,209
关键字:

自定义控件