自定义控件_自定义控件问题 - 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个自定义的控件到测试窗口
    第四步 测试程序
    单击“打开”按钮:
    选择一个图片,打开,显示该图:
    测试成功
    展开全文
  • 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());
                    }
                }
            }
    复制代码

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

    千次下载 热门讨论 2020-07-30 23:30:38
    C#自定义控件
  • 自定义控件

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

    o# 自定义控件 #
    在这里插入图片描述

    Day01

    系统控件回顾

    文本控件 TextView和EditText

    图片控件 ImageView

    按钮控件 Button和ImageButton

    进度条 ProgressBar

    单选按钮 RadioButton和RadioGroup

    复选按钮 CheckBox

    状态开关按钮ToggleButton

    父控件 ViewGroup(LinearLayout, RelativeLayout, FrameLayout)

    时钟控件 AnalogClock和DigitalClock

    日期与时间选择控件DatePicker和TimePicker等

    梳理继承关系
    在这里插入图片描述

    开源项目分享

    https://github.com/Trinea/android-open-project

    https://github.com/lightSky/MaterialDesignCenter

    优酷菜单开发

    • 布局文件开发

        <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" >
      
        <RelativeLayout
            android:id="@+id/rl_level3"
            android:layout_width="280dp"
            android:layout_height="140dp"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:background="@drawable/level3" >
      
            <ImageView
                android:id="@+id/iv_channel1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_margin="10dp"
                android:src="@drawable/channel1" />
      
            <ImageView
                android:id="@+id/iv_channel2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_above="@id/iv_channel1"
                android:layout_alignLeft="@id/iv_channel1"
                android:layout_marginBottom="5dp"
                android:layout_marginLeft="25dp"
                android:src="@drawable/channel2" />
      
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_above="@id/iv_channel2"
                android:layout_alignLeft="@id/iv_channel2"
                android:layout_marginBottom="3dp"
                android:layout_marginLeft="33dp"
                android:src="@drawable/channel3" />
      
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_margin="10dp"
                android:src="@drawable/channel4" />
      
            <ImageView
                android:id="@+id/iv_channel7"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_alignParentRight="true"
                android:layout_margin="10dp"
                android:src="@drawable/channel7" />
      
            <ImageView
                android:id="@+id/iv_channel6"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_above="@id/iv_channel7"
                android:layout_alignRight="@id/iv_channel7"
                android:layout_marginBottom="5dp"
                android:layout_marginRight="25dp"
                android:src="@drawable/channel6" />
      
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_above="@id/iv_channel6"
                android:layout_alignRight="@id/iv_channel6"
                android:layout_marginBottom="3dp"
                android:layout_marginRight="33dp"
                android:src="@drawable/channel5" />
        </RelativeLayout>
      
        <RelativeLayout
            android:id="@+id/rl_level2"
            android:layout_width="180dp"
            android:layout_height="90dp"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:background="@drawable/level2" >
      
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_margin="10dp"
                android:src="@drawable/icon_search" />
      
            <ImageView
                android:id="@+id/iv_menu"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="5dp"
                android:src="@drawable/icon_menu" />
      
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_alignParentRight="true"
                android:layout_margin="10dp"
                android:src="@drawable/icon_myyouku" />
        </RelativeLayout>
      
        <RelativeLayout
            android:id="@+id/rl_level1"
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:background="@drawable/level1" >
      
            <ImageView
                android:id="@+id/iv_home"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_centerVertical="true"
                android:src="@drawable/icon_home" />
        </RelativeLayout>
      
    • 点击事件响应

        ivHome = (ImageView) findViewById(R.id.iv_home);
        ivMenu = (ImageView) findViewById(R.id.iv_menu);
        
        ivHome.setOnClickListener(this);
        ivMenu.setOnClickListener(this);
      
        @Override
        public void onClick(View v) {
        		switch (v.getId()) {
        		case R.id.iv_home:
        			System.out.println("home");
        			break;
        		case R.id.iv_menu:
        			System.out.println("menu");
        			break;
        		default:
        			break;
        		}
        	}
      
    • 事件细节处理

        第三层相对布局盖住了第二层布局, 第二层盖住了第一层, 如果给第三层设置点击事件, 那么第一层和第二层都无法再响应事件了.
      
        rlLevel3.setOnClickListener(this);
      
        解决方法:
        修改布局文件位置, 先写第三层布局,再写第二层, 最后写第一层, 这样可以保证第一层布局在最上面, 第三层在最下面, 那么第三层布局就不能挡住第一层和第二层的点击事件了.	
      
    • 动画工具类开发

        package com.itheima.youkuteach;
      
        import android.view.ViewGroup;
        import android.view.animation.Animation;
        import android.view.animation.RotateAnimation;
        
        /**
         * 处理动画的工具类
         * 
         * @author Kevin
         * 
         */
        public class Tools {
        
        	public static void hideView(ViewGroup view) {
        		hideView(view, 0);
        	}
        
        	public static void showView(ViewGroup view) {
        		showView(view, 0);
        	}
        
        	/**
        	 * 隐藏布局
        	 * 
        	 * @param view
        	 */
        	public static void hideView(ViewGroup view, long delayTime) {
        		RotateAnimation anim = new RotateAnimation(0, 180,
        				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 1);
        		anim.setDuration(500);// 动画执行时间
        		anim.setFillAfter(true);// 保持动画结束的状态
        		anim.setStartOffset(delayTime);// 延时执行动画
        		view.startAnimation(anim);
        
        		// 让父控件的所有子控件在不显示的时候无法点击
        		for (int i = 0; i < view.getChildCount(); i++) {
        			view.getChildAt(i).setEnabled(false);// 禁用点击事件
        		}
        	}
        
        	/**
        	 * 显示布局
        	 * 
        	 * @param view
        	 */
        	public static void showView(ViewGroup view, long delayTime) {
        		RotateAnimation anim = new RotateAnimation(180, 360,
        				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 1);
        		anim.setDuration(500);// 动画执行时间
        		anim.setFillAfter(true);// 保持动画结束的状态
        		anim.setStartOffset(delayTime);// 延时执行动画
        		view.startAnimation(anim);
        
        		for (int i = 0; i < view.getChildCount(); i++) {
        			view.getChildAt(i).setEnabled(true);// 开启点击事件
        		}
        	}
        }	
      
    • 主页面开发

        package com.itheima.youkuteach;
        
        import android.app.Activity;
        import android.os.Bundle;
        import android.view.KeyEvent;
        import android.view.View;
        import android.view.View.OnClickListener;
        import android.widget.ImageView;
        import android.widget.RelativeLayout;
        
        public class MainActivity extends Activity implements OnClickListener {
        
        	private RelativeLayout rlLevel1;
        	private RelativeLayout rlLevel2;
        	private RelativeLayout rlLevel3;
        
        	private ImageView ivHome;
        	private ImageView ivMenu;
        
        	/**
        	 * 判断第一级菜单 是否显示。 true 为显示 false 为隐藏
        	 */
        	private boolean isShowLevel1 = true;
        
        	/**
        	 * 判断第二级菜单 是否显示。 true 为显示 false 为隐藏
        	 */
        	private boolean isShowLevel2 = true;
        
        	/**
        	 * 判断第三级菜单 是否显示。 true 为显示 false 为隐藏
        	 */
        	private boolean isShowLevel3 = true;
        
        	@Override
        	protected void onCreate(Bundle savedInstanceState) {
        		super.onCreate(savedInstanceState);
        		setContentView(R.layout.activity_main);
        
        		rlLevel1 = (RelativeLayout) findViewById(R.id.rl_level1);
        		rlLevel2 = (RelativeLayout) findViewById(R.id.rl_level2);
        		rlLevel3 = (RelativeLayout) findViewById(R.id.rl_level3);
        
        		ivHome = (ImageView) findViewById(R.id.iv_home);
        		ivMenu = (ImageView) findViewById(R.id.iv_menu);
        
        		ivHome.setOnClickListener(this);
        		ivMenu.setOnClickListener(this);
        	}
        
        	@Override
        	public void onClick(View v) {
        		switch (v.getId()) {
        		case R.id.iv_home:
        			System.out.println("home");
        			if (isShowLevel2) {
        				// 隐藏二级菜单
        				Tools.hideView(rlLevel2);
        				isShowLevel2 = false;
        
        				if (isShowLevel3) {
        					// 隐藏三级菜单
        					Tools.hideView(rlLevel3, 200);
        					isShowLevel3 = false;
        				}
        			} else {
        				// 显示二级菜单
        				Tools.showView(rlLevel2);
        				isShowLevel2 = true;
        			}
        			break;
        		case R.id.iv_menu:
        			System.out.println("menu");
        			if (isShowLevel3) {
        				// 隐藏三级菜单
        				Tools.hideView(rlLevel3);
        				isShowLevel3 = false;
        			} else {
        				// 显示三级菜单
        				Tools.showView(rlLevel3);
        				isShowLevel3 = true;
        			}
        			break;
        		default:
        			break;
        		}
        	}
        }
      
    • 拦截物理菜单键

        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
        	if (keyCode == KeyEvent.KEYCODE_MENU) {// 拦截物理菜单键
        		if (isShowLevel1) {// 如果一级菜单显示, 则隐藏所有菜单
        			Tools.hideView(rlLevel1);
        			isShowLevel1 = false;
      
        			if (isShowLevel2) {
        				Tools.hideView(rlLevel2, 200);
        				isShowLevel2 = false;
        			}
      
        			if (isShowLevel3) {
        				Tools.hideView(rlLevel3, 300);
        				isShowLevel3 = false;
        			}
        		} else {// 如果一级菜单隐藏, 则显示一级菜单和二级菜单
        			Tools.showView(rlLevel1);
        			isShowLevel1 = true;
      
        			Tools.showView(rlLevel2, 200);
        			isShowLevel2 = true;
        		}
        		return true;
        	}
        	return super.onKeyDown(keyCode, event);
        }
      

    广告条开发

    • ViewPager介绍

    • ViewPager源码绑定

        选中项目,点右键->Build Path->Configure Build Path->Libraries->删除support v4包->Add Jars(重新添加support v4包)->Order And Export->勾选support v4包->进入ViewPager的类中, 点击Attach Source按钮, 绑定源码
      
    • ViewPager的基本使用

      1. 布局文件

          <android.support.v4.view.ViewPager
        		 android:id="@+id/vp"
        	 	 android:layout_width="wrap_content"
         	 android:layout_height="wrap_content" />
        
      2. 数据适配器

         /**
          * ViewPager的数据适配器
          * 
          * @author Kevin
          * 
          */
         class MyPagerAdapter extends PagerAdapter {
        
         	/**
         	 * item的数量
         	 */
         	@Override
         	public int getCount() {
         		return mImageIds.length;
         	}
        
         	/**
         	 * 判断要绘制的view和object之间的联系 object: 就是instantiateItem返回的object
         	 */
         	@Override
         	public boolean isViewFromObject(View view, Object object) {
         		return view == object;
         	}
        
         	/**
         	 * 初始化布局
         	 */
         	@Override
         	public Object instantiateItem(ViewGroup container, int position) {
         		System.out.println("instantiateItem:" + position);
         		ImageView view = new ImageView(getApplicationContext());
         		view.setImageResource(mImageIds[position]);
         		container.addView(view);
         		return view;
         	}
        
         	/**
         	 * 销毁特定位置上的view object: 就是instantiateItem返回的object
         	 */
         	@Override
         	public void destroyItem(ViewGroup container, int position, Object object) {
         		System.out.println("destroyItem:" + position);
         		container.removeView((View) object);
         	}
         }
        
      3. 初始化ViewPager

         mViewPager = (ViewPager) findViewById(R.id.vp);
         MyPagerAdapter adapter = new MyPagerAdapter();
         mViewPager.setAdapter(adapter);
        
    • ViewPager循环拖动

        1. 将ViewPager的孩子个数改为Int的最大值
      
        	/**
        	 * item的数量
        	 */
        	@Override
        	public int getCount() {
        		//return mImageIds.length;
        		return Integer.MAX_VALUE;
        	}
      
        	如果1秒钟滑动一页的话,需要69年才能够划完, 所以Int的最大值可以基本保证无限循环
      
        2. 修改初始化布局的方法
      
        	/**
        	 * 初始化布局
        	 */
        	@Override
        	public Object instantiateItem(ViewGroup container, int position) {
        		System.out.println("instantiateItem:" + position);
        		ImageView view = new ImageView(getApplicationContext());
        		view.setImageResource(mImageIds[position%mImageIds.length]);//此处应该是当前位置对item个数的取余结果
        		container.addView(view);
        		return view;
        	}
      
        3. 初始化完成后,viewPager展示第一页item, 所以只能向右滑动,无法向左滑动, 为了保证一开始就可以向左滑动,可以设定开始位置为比较大的值,比如:
      
        	mViewPager.setCurrentItem(mImageIds.length * 1000);
      
    • ViewPager增加标题展示

        1. 布局文件
      
        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true" >
      
            <android.support.v4.view.ViewPager
                android:id="@+id/vp"
                android:layout_width="match_parent"
                android:layout_height="180dp" />
      
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:background="#a000"
                android:orientation="vertical" >
      
                <TextView
                    android:id="@+id/tv_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="5dp"
                    android:text="标题"
                    android:textColor="#fff"
                    android:textSize="16sp" />
            </LinearLayout>
        </FrameLayout>
      
        2. ViewPager设置滑动监听
        
        // 图片标题集合
        private final String[] mImageDes = { "巩俐不低俗,我就不能低俗", "朴树又回来啦!再唱经典老歌引万人大合唱","揭秘北京电影如何升级", "乐视网TV版大派送", "热血屌丝的反杀" };
      
        // 设置滑动监听
        mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
      
        	// 某一页被选中时调用
        	@Override
        	public void onPageSelected(int position) {
        		// 更新标题内容
        		tvTitle.setText(mImageDes[position % mImageDes.length]);
        	}
      
        	// 滑动时调用
        	@Override
        	public void onPageScrolled(int position, float positionOffset,
        			int positionOffsetPixels) {
      
        	}
      
        	// 滑动状态发生变化
        	@Override
        	public void onPageScrollStateChanged(int state) {
      
        	}
        });
      
        3. 保证图片宽高填充ImageView
      
        	view.setScaleType(ScaleType.FIT_XY);//图片宽高填充父窗体
      
    • ViewPager增加位置指示器

        1. 布局文件中写一个空的线性布局   
         	
        	<LinearLayout
                android:id="@+id/ll_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="3dp"
                android:gravity="center"
                android:orientation="horizontal" >
            </LinearLayout>
      
        2. 使用shape绘制小圆点
      
        	shape_point_default.xml
        	
        	<?xml version="1.0" encoding="utf-8"?>
        	<shape xmlns:android="http://schemas.android.com/apk/res/android"
        	    android:shape="oval" >
        	
        	    <size
        	        android:height="5dp"
        	        android:width="5dp" />
        	
        	    <solid android:color="#5fff" />
        	
        	</shape>
      
        	------------------------------------
      
        	shape_point_selected.xml
      
        	<?xml version="1.0" encoding="utf-8"?>
        	<shape xmlns:android="http://schemas.android.com/apk/res/android"
        	    android:shape="oval" >
        	
        	    <size
        	        android:height="5dp"
        	        android:width="5dp" />
        	
        	    <solid android:color="#f00" />
        	
        	</shape>
      
        	-----------------------------------
      
        	point_selecter.xml
      
        	<?xml version="1.0" encoding="utf-8"?>
        	<selector xmlns:android="http://schemas.android.com/apk/res/android">
        	
        	    <item android:drawable="@drawable/shape_point_selected" android:state_enabled="true"/>
        	    <item android:drawable="@drawable/shape_point_default" android:state_enabled="false"/>
        	
        	</selector>
        	
      
        3. 初始化小圆点
      
        	for (int i = 0; i < mImageIds.length; i++) {
        		ImageView ivPoint = new ImageView(this);
        		ivPoint.setImageResource(R.drawable.point_selecter);
      
        		// 默认第一个圆点选中,其他不选中
        		if (i != 0) {
        			ivPoint.setEnabled(false);
        		} else {
        			ivPoint.setEnabled(true);
        		}
      
        		// 初始化布局参数
        		LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
        				LayoutParams.WRAP_CONTENT);
      
        		if (i != 0) {
        			params.leftMargin = 5;// 增加圆点之间的边距
        		}
      
        		ivPoint.setLayoutParams(params);// 设置布局参数
        		llContainer.addView(ivPoint);// 给线性布局添加孩子
        	}
      
        4. 滑动ViewPager时, 更新小圆点的展示
      
        	// 某一页被选中时调用
        	@Override
        	public void onPageSelected(int position) {
        		int pos = position % mImageDes.length;// 获取图片位置
      
        		// 更新标题内容
        		tvTitle.setText(mImageDes[pos]);
      
        		// 将当前圆点设置为选中状态
        		ImageView ivPoint = (ImageView) llContainer.getChildAt(pos);
        		ivPoint.setEnabled(true);
      
        		// 将上一个圆点设置为不选中状态
        		llContainer.getChildAt(mLastPointPos).setEnabled(false);
      
        		// 重新设置上次圆点的位置
        		mLastPointPos = pos;
        	}
      
    • ViewPager实现页面自动切换

        //此Handler专门处理轮播条的自动切换
        private Handler mHandler = new Handler() {
        	public void handleMessage(android.os.Message msg) {
        		int currentItem = mViewPager.getCurrentItem();
        		mViewPager.setCurrentItem(++currentItem);// 设置当前页面为下一页
      
        		mHandler.sendEmptyMessageDelayed(0, 3000);// 延时3秒后发送消息,自动更新轮播条位置
        	};
        };
      
        // 延时3秒后发送消息,自动更新轮播条位置
        mHandler.sendEmptyMessageDelayed(0, 3000);
      
    • ViewPager事件处理

        当手动滑动轮播条时, 页面还是会自动切换. 可以改动为, 手动触摸轮播条时停止切换, 手松开后再继续切换.
      
        // 设置触摸监听
        mViewPager.setOnTouchListener(new OnTouchListener() {
      
        	@Override
        	public boolean onTouch(View v, MotionEvent event) {
        		switch (event.getAction()) {
        		case MotionEvent.ACTION_DOWN:
        			System.out.println("ACTION_DOWN");
        			mHandler.removeCallbacksAndMessages(null);// 删除所有消息,停止广告条自动切换
        			break;
        		case MotionEvent.ACTION_UP:
        			System.out.println("ACTION_UP");
        			mHandler.sendEmptyMessageDelayed(0, 3000);// 继续自动切换广告条
        			break;
      
        		default:
        			break;
        		}
      
        		return false;// 这里需要返回false, 不能消耗掉事件,
        						// 这样的话ViewPager才能够响应触摸滑动的事件,页面跟随手指移动
        	}
        });
      

    下拉框

    - 布局文件开发
    
    	<RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="152dp" >
    
            <EditText
                android:id="@+id/et_input"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ems="10" >
            </EditText>
    
            <ImageView
                android:id="@+id/iv_down"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignRight="@id/et_input"
                android:src="@drawable/down_arrow" />
        </RelativeLayout>
    
    - 相关代码
    
    	public class MainActivity extends Activity {
    
    		private EditText etInput;
    		private ImageView ivDown;
    	
    		private ArrayList<String> mList;
    		private PopupWindow mPopupWindow;
    		private ListView lvList;
    	
    		@Override
    		protected void onCreate(Bundle savedInstanceState) {
    			super.onCreate(savedInstanceState);
    			setContentView(R.layout.activity_main);
    			etInput = (EditText) findViewById(R.id.et_input);
    			ivDown = (ImageView) findViewById(R.id.iv_down);
    	
    			ivDown.setOnClickListener(new OnClickListener() {
    	
    				@Override
    				public void onClick(View v) {
    					showPopup();
    				}
    			});
    	
    			// 初始化下拉列表数据
    			mList = new ArrayList<String>();
    			for (int i = 0; i < 200; i++) {
    				mList.add("aaabbbccc" + i);
    			}
    	
    			// 初始化listview
    			lvList = new ListView(this);
    			lvList.setAdapter(new MyAdapter());
    	
    			lvList.setOnItemClickListener(new OnItemClickListener() {
    	
    				@Override
    				public void onItemClick(AdapterView<?> parent, View view,
    						int position, long id) {
    					String text = mList.get(position);
    					etInput.setText(text);
    	
    					mPopupWindow.dismiss();
    				}
    			});
    		}
    	
    		/**
    		 * 展现下拉框
    		 */
    		protected void showPopup() {
    			if (mPopupWindow == null) {
    				mPopupWindow = new PopupWindow(lvList, etInput.getWidth(), 200,
    						true);
    				mPopupWindow.setBackgroundDrawable(new ColorDrawable());
    			}
    	
    			mPopupWindow.showAsDropDown(etInput);//展现在文本框的正下方
    		}
    	
    		class MyAdapter extends BaseAdapter {
    	
    			@Override
    			public int getCount() {
    				return mList.size();
    			}
    	
    			@Override
    			public String getItem(int position) {
    				return mList.get(position);
    			}
    	
    			@Override
    			public long getItemId(int position) {
    				return position;
    			}
    	
    			@Override
    			public View getView(final int position, View convertView,
    					ViewGroup parent) {
    				ViewHolder holder;
    				if (convertView == null) {
    					convertView = View.inflate(getApplicationContext(),
    							R.layout.list_item, null);
    					holder = new ViewHolder();
    					holder.tvText = (TextView) convertView
    							.findViewById(R.id.tv_text);
    					holder.ivDelete = (ImageView) convertView
    							.findViewById(R.id.iv_delete);
    					convertView.setTag(holder);
    				} else {
    					holder = (ViewHolder) convertView.getTag();
    				}
    	
    				// TextView view = new TextView(getApplicationContext());
    				// String text = getItem(position);
    				// view.setText(text);
    	
    				holder.tvText.setText(getItem(position));
    				holder.ivDelete.setOnClickListener(new OnClickListener() {
    	
    					@Override
    					public void onClick(View v) {
    						//删除当前被点击的元素,并刷新listview
    						mList.remove(position);
    						MyAdapter.this.notifyDataSetChanged();
    					}
    				});
    	
    				return convertView;
    			}
    	
    		}
    	
    		static class ViewHolder {
    			public TextView tvText;
    			public ImageView ivDelete;
    		}
    	}
    

    自定义开关

    • 创建自定义View

        /**
         * 自定义开关
         * 
         * @author Kevin
         * 
         */
        public class MySwitch extends View {
      
        	// 代码初始化
        	public MySwitch(Context context) {
        		super(context);
        		init();
        	}
        
        	// 布局文件初始化,带属性和样式
        	public MySwitch(Context context, AttributeSet attrs, int defStyle) {
        		super(context, attrs, defStyle);
        		init();
        	}
        
        	// 布局文件初始化, 带属性
        	public MySwitch(Context context, AttributeSet attrs) {
        		super(context, attrs);
        		init();
        	}
        }
      
    • 重写onDraw方法, 绘制矩形

        @Override
        protected void onDraw(Canvas canvas) {
        	System.out.println("onDraw");
        	canvas.drawRect(0, 0, 200, 200, mPaint);//绘制矩形
        }
      
    • 重新onMeasure方法, 修改控件大小

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        	System.out.println("onMeasure");
        	setMeasuredDimension(50, 50);
        }
      
        注意: onMeasure先于onDraw调用, 最终绘制大小以onMeasure设置的大小为准
      
    • 绘制自定义开关的两张图片,修改滑块位置,展示开关效果

        /**
         * 初始化
         */
        private void init() {
        	// 初始化画笔对象
        	mPaint = new Paint();
        	mPaint.setColor(Color.RED);
      
        	// 初始化图片对象
        	mBitmapBg = BitmapFactory.decodeResource(getResources(),
        			R.drawable.switch_background);
        	mBitmapSlide = BitmapFactory.decodeResource(getResources(),
        			R.drawable.slide_button);
      
        	// 计算滑块最大左边距
        	MAX_LEFT = mBitmapBg.getWidth() - mBitmapSlide.getWidth();
        }
      
        @Override
        protected void onDraw(Canvas canvas) {
        	System.out.println("onDraw");
        	canvas.drawBitmap(mBitmapBg, 0, 0, mPaint);// 绘制图片
        	canvas.drawBitmap(mBitmapSlide, 0, 0, mPaint);//修改参2值为0或者MAX_LEFT, 展示开关效果
        }
      
    • 点击事件处理(点击后切换开关展示)

        private int mSlideLeft;// 滑块左边距
        private boolean isOpen;// true表示开关已打开, false表示已关闭
      
        setOnClickListener(new OnClickListener() {
      
        	@Override
        	public void onClick(View v) {
        		if (isOpen) {
        			isOpen = false;
        			mSlideLeft = 0;
        		} else {
        			isOpen = true;
        			mSlideLeft = MAX_LEFT;
        		}
      
        		invalidate();// 刷新view, 会重新调用onDraw方法
        	}
        });
      
        canvas.drawBitmap(mBitmapSlide, mSlideLeft, 0, mPaint);
      
    • 自定义回调接口,响应开关切换事件

    • (当某一个界面需知道该控件的状态时。可以设回调监听)

        对比CheckBox的开关事件, 给自定义View设置类似的事件监听.
      
        private OnCheckedChangeListener mListener;
      
        /**
         * 1,定义开关切换事件的回调接口
         */定义
        public interface OnCheckedChangeListener {
         //回调的方法   及回传的参数
        	public void onCheckedChanged(MySwitch view, boolean isChecked);
        }
      
        /**
         * 设置开关切换的事件监听的方法
         */
        public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        	mListener = listener;
        }
      
        //在OnClick事件中调用接口方法(回调方法,回调当前开关状态)
        if (mListener != null) {
        		mListener.onCheckedChanged(MySwitch.this, isOpen);
        }
      
        //在MainActivity(前端界面)设置事件监听
        MySwitch s = (MySwitch) findViewById(R.id.my_switch);
        s.setOnCheckedChangeListener(new OnCheckedChangeListener() {
      
        	@Override
        	public void onCheckedChanged(MySwitch view, boolean isChecked) {
        		System.out.println("开关状态:" + isChecked);
        	}
        });
      
    • 滑动事件处理

        int startX;//起始x坐标
      
        @Override
        public boolean onTouchEvent(MotionEvent event) {
        	switch (event.getAction()) {
        	case MotionEvent.ACTION_DOWN:
        		startX = (int) event.getX();
        		break;
        	case MotionEvent.ACTION_MOVE:
        		int endX = (int) event.getX();
        		int dx = endX - startX;
      
        		mSlideLeft += dx;//根据x坐标移动偏移量,计算最新的左边距值
      
        		//修正移动位置,避免过于偏左
        		if (mSlideLeft < 0) {
        			mSlideLeft = 0;
        		}
      
        		//修正移动位置,避免过于偏右
        		if (mSlideLeft > MAX_LEFT) {
        			mSlideLeft = MAX_LEFT;
        		}
      
        		//刷新view
        		invalidate();
      
        		//重新初始化起始点坐标
        		startX = (int) event.getX();
      
        		break;
      
        	case MotionEvent.ACTION_UP:
        		//手指抬起后,根据当前滑块位置,确定开关状态
        		if (mSlideLeft > MAX_LEFT / 2) {//打开状态
        			mSlideLeft = MAX_LEFT;
        			isOpen = true;
        		} else {//关闭状态
        			mSlideLeft = 0;
        			isOpen = false;
        		}
      
        		invalidate();//刷新
      
        		//开关切换事件的回调方法
        		if (mListener != null) {
        			mListener.onCheckedChanged(MySwitch.this, isOpen);
        		}
        		break;
      
        	default:
        		break;
        	}
      
        	return true;//消耗掉事件
        }
      
    • 同时响应点击事件和触摸事件

        boolean isClick;// 表示当前是否可以响应点击事件
      
        @Override
        public boolean onTouchEvent(MotionEvent event) {
        	switch (event.getAction()) {
        	case MotionEvent.ACTION_DOWN:
        		startX = (int) event.getX();
        		break;
        	case MotionEvent.ACTION_MOVE:
        		int endX = (int) event.getX();
        		int dx = endX - startX;
      
        		mMoveX += Math.abs(dx);// 计算移动距离
      
        		mSlideLeft += dx;// 根据x坐标移动偏移量,计算最新的左边距值
      
        		// 修正移动位置,避免过于偏左
        		if (mSlideLeft < 0) {
        			mSlideLeft = 0;
        		}
      
        		// 修正移动位置,避免过于偏右
        		if (mSlideLeft > MAX_LEFT) {
        			mSlideLeft = MAX_LEFT;
        		}
      
        		// 刷新view
        		invalidate();
      
        		// 重新初始化起始点坐标
        		startX = (int) event.getX();
        		break;
      
        	case MotionEvent.ACTION_UP:
        		if (mMoveX >= 5) {// 如果移动距离大于等于5个像素,认为触发了移动事件,此时不允许响应单击事件
        			isClick = false;
        		} else {
        			isClick = true;
        		}
      
        		mMoveX = 0;// 移动距离归零
      
        		if (!isClick) {
        			// 手指抬起后,根据当前滑块位置,确定开关状态
        			if (mSlideLeft > MAX_LEFT / 2) {// 打开状态
        				mSlideLeft = MAX_LEFT;
        				isOpen = true;
        			} else {// 关闭状态
        				mSlideLeft = 0;
        				isOpen = false;
        			}
      
        			invalidate();// 刷新
      
        			// 开关切换事件的回调方法
        			if (mListener != null) {
        				mListener.onCheckedChanged(MySwitch.this, isOpen);
        			}
        		}
      
        		break;
      
        	default:
        		break;
        	}
      
        	return super.onTouchEvent(event);// 此处改成父类方法, 可以同时响应onClick和onTouchEvent
        }
      
        ----------------------------------
      
        setOnClickListener(new OnClickListener() {
      
        	@Override
        	public void onClick(View v) {
        		if (isClick) {
        			if (isOpen) {
        				isOpen = false;
        				mSlideLeft = 0;
        			} else {
        				isOpen = true;
        				mSlideLeft = MAX_LEFT;
        			}
      
        			if (mListener != null) {
        				mListener.onCheckedChanged(MySwitch.this, isOpen);
        			}
      
        			invalidate();// 刷新view, 会重新调用onDraw方法
        		}
        	}
        });
      

    Day02

    自定义开关

    • 自定义属性

        attrs.xml
      
        <?xml version="1.0" encoding="utf-8"?>
        <resources>
      
            <declare-styleable name="MySwitch">
                <attr name="isChecked" format="boolean" />
                <attr name="slideDrawable" format="reference" />
            </declare-styleable>
      
        </resources>
      
        -----------------------------------------------------------
      
        activity_main.xml
      
        <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.myswitch"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        
            <com.itheima.myswitch.MySwitch
                android:id="@+id/my_switch"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                itheima:isChecked="false"
                itheima:slideDrawable="@drawable/slide_button" />
        
        </RelativeLayout>
      
        ------------------------------------------
      
        // 布局文件初始化, 带属性
        public MySwitch(Context context, AttributeSet attrs) {
        	super(context, attrs);
        	init();
      
        	// 自定义属性
        	isOpen = attrs.getAttributeBooleanValue(
        			"http://schemas.android.com/apk/res/com.itheima.myswitch",
        			"isChecked", false);// 获取当前选中状态
      
        	int slideId = attrs.getAttributeResourceValue(
        			"http://schemas.android.com/apk/res/com.itheima.myswitch",
        			"slideDrawable", 0);//获取滑块图片
        	
        	if (slideId > 0) {
        		mBitmapSlide = BitmapFactory
        				.decodeResource(getResources(), slideId);//加载滑块图片
        	}
      
        	// 根据当前选中状态,更新界面展示
        	if (isOpen) {
        		mSlideLeft = MAX_LEFT;
        	} else {
        		mSlideLeft = 0;
        	}
      
        	invalidate();
        }
      

    自定义ViewPager

    • 创建一个类, 继承ViewGroup

        public class MyViewPager extends ViewGroup {
      
        	public MyViewPager(Context context, AttributeSet attrs, int defStyle) {
        		super(context, attrs, defStyle);
        		init();
        	}
        
        	public MyViewPager(Context context, AttributeSet attrs) {
        		super(context, attrs);
        		init();
        	}
        
        	public MyViewPager(Context context) {
        		super(context);
        		init();
        	}
        }
      
    • 在MainActivity中给自定义ViewPager添加子元素

        mViewPager = (MyViewPager) findViewById(R.id.my_scroll);
      
        //给自定义的ViewPager动态添加图片
        for (int i = 0; i < mImageIds.length; i++) {
        	ImageView view = new ImageView(this);//创建ImageView对象
        	view.setBackgroundResource(mImageIds[i]);//设置图片背景(背景可以保证填充ImageView)
        	mViewPager.addView(view);//添加一个ImageView
        }
      
    • 添加完图片之后, 保证图片可以一字排开

        /**
         * 获得当前view的位置, 如果当前view是ViewGroup的话,应在此方法中,指定子View的位置
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
        	// 遍历所有子View, 指定每个子View的位置,保证一字排开
        	for (int i = 0; i < getChildCount(); i++) {
        		getChildAt(i).layout(0 + i * getWidth(), 0,
        				getWidth() + i * getWidth(), getHeight());
        	}
        }
      
    • 滑动自定义View, 实现页面切换

        private void init() {
        //初始化手势识别器
        mDetector = new GestureDetector(getContext(),
        		new GestureDetector.SimpleOnGestureListener() {
        			@Override
        			public boolean onScroll(MotionEvent e1, MotionEvent e2,
        					float distanceX, float distanceY) {
        				scrollBy((int) distanceX, 0);//水平偏移距离
        				return super.onScroll(e1, e2, distanceX, distanceY);
        			}
        		});
        }
      
        @Override
        public boolean onTouchEvent(MotionEvent event) {
        	mDetector.onTouchEvent(event);
        	switch (event.getAction()) {
        	case MotionEvent.ACTION_UP:
        		//计算当前应该滑动的具体位置
        		int index = (getScrollX() + getWidth() / 2) / getWidth();
      
        		//避免位置越界
        		if (index > getChildCount() - 1) {
        			index = getChildCount() - 1;
        		}
      
        		//滑动到目标位置
        		scrollTo(index * getWidth(), 0);
        		break;
      
        	default:
        		break;
        	}
      
        	return true;
        }
      
    • 滑动时,页面切换的动画效果

        //初始化滑动器
        mScroller = new Scroller(getContext());
        -------------------------------
        //计算当前应该滑动的具体位置
        int index = (getScrollX() + getWidth() / 2) / getWidth();
      
        //避免位置越界
        if (index > getChildCount() - 1) {
        	index = getChildCount() - 1;
        }
      
        //计算应该滑动的距离
        int distance = index * getWidth() - getScrollX();
        mScroller.startScroll(getScrollX(), 0, distance, 0, Math.abs(distance));// 参1:起始x;参2:起始y;参3:x偏移;参4:y偏移;参5:滑动时间(此处以距离的绝对值作为时间,保证了距离越长,时间越长)
        invalidate();//刷新界面
      
        -------------------------------
        调用startScroll方法之后, 会回调computeScroll方法,计算当前的移动情况
        @Override
        public void computeScroll() {
        	if (mScroller.computeScrollOffset()) {//判断是否移动结束
        		int currX = mScroller.getCurrX();//获取当前应该滑动的水平位置
        		scrollTo(currX, 0);//滑动
      
        		invalidate();//刷新界面
        	}
        }
      
    • 给自定义ViewPager添加一个测试页面,处理事件监听

        item_view.xml
      
        <?xml version="1.0" encoding="utf-8"?>
        <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical" >
        
                <TextView
                    android:id="@+id/textView1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TextView" />
        
                <Button
                    android:id="@+id/button1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Button" />
        
                <Button
                    android:id="@+id/button2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Button" />
        
                <Button
                    android:id="@+id/button3"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Button" />
        
                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TextView" />
        
                <CheckedTextView
                    android:id="@+id/checkedTextView1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="CheckedTextView" />
        
                <ProgressBar
                    android:id="@+id/progressBar1"
                    style="?android:attr/progressBarStyleLarge"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
        
                <Button
                    android:id="@+id/button4"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Button" />
        
                <TextView
                    android:id="@+id/textView3"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TextView" />
        
                <TextView
                    android:id="@+id/textView4"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TextView" />
        
                <TextView
                    android:id="@+id/textView5"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TextView" />
        
                <TextView
                    android:id="@+id/textView6"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TextView" />
        
                <TextView
                    android:id="@+id/textView7"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
        
                <TextView
                    android:id="@+id/textView8"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
        
                <TextView
                    android:id="@+id/textView9"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
        
                <TextView
                    android:id="@+id/textView9"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
        
                <TextView
                    android:id="@+id/textView9"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
        
                <TextView
                    android:id="@+id/textView9"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
        
                <TextView
                    android:id="@+id/textView9"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
        
                <TextView
                    android:id="@+id/textView9"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceLarge" />
            </LinearLayout>
        
        </ScrollView>
      
        这个布局文件用ScrollView包裹, 可以上下滑动
      
        ----------------------------
        //初始化测试页面
        View item = View.inflate(this, R.layout.item_view, null);
        //将测试页面添加到第三个位置
        mViewPager.addView(item, 2);
      
        ----------------------------
        问题: 运行测试, 发现无法展现测试页面布局, 界面是一块白板.这是因为自定义ViewPager在测量孩子的大小时, 只测量了测试页面根布局(ScrollView)的大小,而没有理会ScrollView下孩子的大小,所以系统由于不知道ScrollView的孩子大小,所以就无法绘制.
      
        解决方法: 
        /**
         * 对本view进行测量大小,
         * 如果当前view是viewGroup,那么需要对每一个子view进行测量大小
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      
        	for (int i = 0; i < getChildCount(); i++) {
        		getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
        	}
      
        	// int size = MeasureSpec.getSize(widthMeasureSpec);//获取尺寸大小
        	// int mode = MeasureSpec.getMode(widthMeasureSpec);//获取尺寸模式
        }
      
        ----------------------------------
      
        问题: 运行测试, 发现当页面切换到测试页面时,只支持上下滑动,无法左右滑动了. 这是因为自定义ViewPager的孩子,也就是我们的测试页面,拦截了事件, 导致自定义ViewPager无法响应事件了.
      
        解决方案: 重写onInterceptTouchEvent方法,用于拦截事件
        
        /**
         * 事件拦截
         */
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
        	return true;//返回true,表示事件被拦截,不会继续向下传递
        }
      
        -------------------------------
      
        //拦截之后,发现自定义ViewPager可以左右滑动了,但是测试页面无法上下滑动, 因为上下滑动的事件已经被他的父亲(自定义ViewPager)拦截了. 所以应该有条件的拦截事件,而不是全部拦截
      
        /**
         * 事件拦截
         */
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
        	switch (ev.getAction()) {
        	case MotionEvent.ACTION_DOWN:
        		//解决 onTouchEvent 执行时,没有down事件,导致计算出错的BUG
        		mDetector.onTouchEvent(ev);
      
        		startX = (int) ev.getX();
        		startY = (int) ev.getY();
        		break;
        	case MotionEvent.ACTION_MOVE:
        		int endX = (int) ev.getX();
        		int endY = (int) ev.getY();
      
        		int dx = Math.abs(endX - startX);
        		int dy = Math.abs(endY - startY);
      
        		if (dx > dy) {
        			return true;
        		}
      
        		break;
      
        	default:
        		break;
        	}
      
        	return false;
        }
      
        -----------------------
      
        /**
         * 事件分发, 此方法先于onInterceptTouchEvent调用(了解)
         */
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
        	return super.dispatchTouchEvent(ev);
        }
      
    • RaidoGroup/RadioButton

        <RadioGroup
            android:id="@+id/rg_group"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >
        </RadioGroup>
      
        ----------------------
      
        //根据图片数量,动态增加RadioButton(注意增加测试页面)
        for (int i = 0; i < mImageIds.length + 1; i++) {
        	RadioButton rb = new RadioButton(this);
        	rb.setId(i);//以当前item的位置作为id
        	rgGroup.addView(rb);
        }
      
        rgGroup.check(0);// 默认选中第一项
      
        ------------------------
      
        //滑动自定义ViewPager的事件监听
      
        private OnPageChangeListener mListener;
      
        /**
         * 开关切换事件的回调接口
         */
        public interface OnPageChangeListener {
        	public void onPageSelected(int position);
        }
      
        /**
         * 设置开关切换的事件监听
         */
        public void setOnPageChangeListener(OnPageChangeListener listener) {
        	mListener = listener;
        }
      
        /**
         * 设置当前选中页
         * 
         * @param positon
         */
        public void setCurrentPage(int positon) {
        	// 计算应该滑动的距离
        	int distance = positon * getWidth() - getScrollX();
        	mScroller.startScroll(getScrollX(), 0, distance, 0, Math.abs(distance));// 参1:起始x;参2:起始y;参3:x偏移;参4:y偏移;参5:滑动时间
        	// scrollTo(index * getWidth(), 0);
        	invalidate();// 刷新界面
      
        	//页面切换后的回调方法
        	if (mListener != null) {
        		mListener.onPageSelected(positon);
        	}
        }
      
        ----------------------
      
        //滑动自定义ViewPager,切换RadioButton的选中状态
        mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
      
        	@Override
        	public void onPageSelected(int position) {
        		rgGroup.check(position);
        	}
        });
      
        ---------------------
      
        //点击RadioButton, 切换当前页面
        rgGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
      
        	@Override
        	public void onCheckedChanged(RadioGroup group, int checkedId) {
        		mViewPager.setCurrentPage(checkedId);
        	}
        });
      

    水波纹效果

    • 简单的自定义View

      绘制线条和圆, 熟悉绘制方法

        /**
         * 简单的自定义view
         * 
         * @author Kevin
         * 
         */
        public class MyRingSimple extends View {
        
        	private Paint mPaint;
        	private Paint mCirclePaint;
        
        	public MyRingSimple(Context context, AttributeSet attrs, int defStyle) {
        		super(context, attrs, defStyle);
        		initView();
        	}
        
        	public MyRingSimple(Context context, AttributeSet attrs) {
        		super(context, attrs);
        		initView();
        	}
        
        	public MyRingSimple(Context context) {
        		super(context);
        		initView();
        	}
        
        	@Override
        	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        		// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        		setMeasuredDimension(200, 200);// 重新定义尺寸
        	}
        
        	/**
        	 * 初始化布局
        	 */
        	private void initView() {
        		mPaint = new Paint();
        		mPaint.setColor(Color.GREEN);// 设置颜色
        		mPaint.setStrokeWidth(3);// 设置宽度
        
        		mCirclePaint = new Paint();
        		mCirclePaint.setColor(Color.RED);
        		mCirclePaint.setStrokeWidth(2);//线条宽度
        		mCirclePaint.setStyle(Style.STROKE);//空心圆
        		mCirclePaint.setAntiAlias(true);//设置没有锯齿
        	}
        
        	@Override
        	protected void onDraw(Canvas canvas) {
        		for (int i = 10; i < getWidth(); i += 20) {
        			// 画线
        			// 参1:起点x, 参 2:起点y,参3:终点x,参4:终点y, 参5:画笔
        			canvas.drawLine(0, i, getWidth(), i, mPaint);// 水平线
        			canvas.drawLine(i, 0, i, getHeight(), mPaint);// 竖直线
        		}
        
        		canvas.translate(-20, -20);// 移动画布
        
        		// 画圆
        		canvas.drawCircle(getWidth() / 2, getHeight() / 2, 50, mCirclePaint);// 参1:圆心x坐标,参2:圆心y坐标,参3:圆半径,参4:画笔
        	}
        }
      
    • 点击屏幕,绘制圆环

        /**
         * 绘制圆环
         * 
         * @author Kevin
         * 
         */
        public class MyRing extends View {
        
        	private int cx;// 圆心x坐标
        	private int cy;// 圆心y坐标
        	private int radius;// 圆环半径
        
        	private Paint mPaint;
        
        	private Handler mHandler = new Handler() {
        		public void handleMessage(android.os.Message msg) {
        			// 渐变
        			// 半径变大
        			// 圆环宽度变大
        			int alpha = mPaint.getAlpha();// 获取当前渐变值
        
        			// 渐变值递减
        			alpha -= 10;
        			if (alpha < 0) {// 如果渐变值小于0,就置为0
        				alpha = 0;
        			}
        
        			mPaint.setAlpha(alpha);// 设置渐变
        			radius += 5;// 半径递增
        			mPaint.setStrokeWidth(radius / 3);// 设置圆环宽度
        			invalidate();// 重新绘制圆环
        
        			if (alpha > 0) {// 渐变值大于0,表示圆环还未完全消失
        				mHandler.sendEmptyMessageDelayed(0, 50);// 继续发送延时消息,形成递归循环
        			}
        		};
        	};
        
        	public MyRing(Context context, AttributeSet attrs, int defStyle) {
        		super(context, attrs, defStyle);
        		initView();
        	}
        
        	public MyRing(Context context, AttributeSet attrs) {
        		super(context, attrs);
        		initView();
        	}
        
        	public MyRing(Context context) {
        		super(context);
        		initView();
        	}
        
        	private void initView() {
        		// 初始化画笔
        		radius = 0;
        		mPaint = new Paint();
        		mPaint.setColor(Color.GREEN);
        		mPaint.setStyle(Style.STROKE);
        		mPaint.setStrokeWidth(radius / 3);
        		mPaint.setAntiAlias(true);
        		mPaint.setAlpha(255);
        	}
        
        	@Override
        	protected void onDraw(Canvas canvas) {
        		canvas.drawCircle(cx, cy, radius, mPaint);// 绘制圆环
        	}
        
        	/**
        	 * 开启圆环动画
        	 */
        	private void startAnim() {
        		mHandler.sendEmptyMessageDelayed(0, 50);
        	}
        
        	@Override
        	public boolean onTouchEvent(MotionEvent event) {
        		if (event.getAction() == MotionEvent.ACTION_DOWN) {
        			// 按下后,获取当前的x,y坐标,作为圆心坐标
        			cx = (int) event.getX();
        			cy = (int) event.getY();
        			// invalidate();
        			initView();// 初始化画笔
        			startAnim();// 开始绘制圆环
        		}
        
        		return super.onTouchEvent(event);
        	}
        
        }
      
    • 移动屏幕,绘制多个圆环

        /**
         * 滑动绘制圆环集合
         * 
         * @author Kevin
         * 
         */
        public class MyRingWave extends View {
        
        	private static final int MIN_DIS = 10;// 两个圆环的最短距离
        
        	// 圆环集合
        	ArrayList<Wave> mWaveList = new ArrayList<MyRingWave.Wave>();
        
        	// 颜色集合
        	int[] mColors = new int[] { Color.RED, Color.GREEN, Color.BLUE,
        			Color.YELLOW };
        
        	private Handler mHandler = new Handler() {
        		public void handleMessage(android.os.Message msg) {
        			flushData();
        			invalidate();
        
        			if (!mWaveList.isEmpty()) {
        				mHandler.sendEmptyMessageDelayed(0, 50);
        			}
        		};
        	};
        
        	public MyRingWave(Context context, AttributeSet attrs, int defStyle) {
        		super(context, attrs, defStyle);
        	}
        
        	public MyRingWave(Context context, AttributeSet attrs) {
        		super(context, attrs);
        	}
        
        	public MyRingWave(Context context) {
        		super(context);
        	}
        
        	@Override
        	protected void onDraw(Canvas canvas) {
        		// 绘制所有圆环
        		for (Wave wave : mWaveList) {
        			canvas.drawCircle(wave.cx, wave.cy, wave.radius, wave.paint);
        		}
        	}
        
        	/**
        	 * 刷新数据
        	 */
        	private void flushData() {
        		ArrayList<Wave> removeList = new ArrayList<MyRingWave.Wave>();// 待移除的圆环对象集合
        		for (Wave wave : mWaveList) {
        			wave.radius += 3;// 增加半径
        			wave.paint.setStrokeWidth(wave.radius / 3);// 重新这是圆环宽度
        			int alpha = wave.paint.getAlpha();
        
        			if (alpha == 0) {// 如果圆环透明度已经为0,不会再进行绘制,需要从列表中移除
        				removeList.add(wave);// 要删除的元素
        				continue;
        			}
        
        			alpha -= 5;// 透明度递减
        			if (alpha < 0) {
        				alpha = 0;
        			}
        
        			wave.paint.setAlpha(alpha);
        		}
        
        		mWaveList.removeAll(removeList);// 移除已经消失的圆环对象
        	}
        
        	@Override
        	public boolean onTouchEvent(MotionEvent event) {
        		switch (event.getAction()) {
        		case MotionEvent.ACTION_DOWN:
        		case MotionEvent.ACTION_MOVE:
        			int cx = (int) event.getX();
        			int cy = (int) event.getY();
        
        			addPoint(cx, cy);
        			break;
        
        		default:
        			break;
        		}
        
        		return true;
        	}
        
        	private void addPoint(int cx, int cy) {
        		if (mWaveList.isEmpty()) {// 第一次添加圆环
        			addWave(cx, cy);
        			mHandler.sendEmptyMessage(0);// 发送消息,启动绘制
        		} else {
        			Wave wave = mWaveList.get(mWaveList.size() - 1);// 获取最后一个圆环
        			if (Math.abs(wave.cx - cx) > MIN_DIS
        					|| Math.abs(wave.cy - cy) > MIN_DIS) {// 只有在两个圆环距离超过一定范围时才进行绘制,保证圆环彼此保持一定间距
        				addWave(cx, cy);
        			}
        		}
        	}
        
        	/**
        	 * 添加圆环对象
        	 * 
        	 * @param cx
        	 * @param cy
        	 */
        	private void addWave(int cx, int cy) {
        		Wave wave = new Wave();
        		wave.cx = cx;
        		wave.cy = cy;
        
        		// 初始化画笔
        		Paint paint = new Paint();
        		paint.setColor(mColors[(int) (Math.random() * 4)]);// 画笔颜色随机
        		paint.setStyle(Style.STROKE);// 绘制空心圆
        		paint.setAntiAlias(true);// 没有锯齿
        
        		wave.paint = paint;
        
        		mWaveList.add(wave);
        	}
        
        	/**
        	 * 圆环对象封装
        	 */
        	class Wave {
        		int cx;// 圆心x坐标
        		int cy;// 圆心y坐标
        		int radius;// 圆环半径
        		Paint paint;// 画笔
        	}
        }
      
    展开全文
  • 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.添加控件,组合成一个新的控件 自定...
  • 如何自定义控件

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

    千次阅读 2018-04-18 20:46:39
    一、前言 最近做一个项目又一次用到类似像微信... 这回使用的Android的自定义控件中的组合控件。二、自定义组合控件原理 项目中经常会遇见很多相似或者相同的布局,比如APP的标题栏,通过将这些布局抽取出来,放...
  • 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):继承自原有控件,添加一些新的
  • Android自定义控件三部曲文章索引

    万次阅读 多人点赞 2019-09-17 19:53:00
    前言:在我从C++转到Android时,就被Android里炫彩斑斓的自定义控件深深折服,想知道如果想利用C++实现这些功能,那是相当困难的。从那时候起,我就想,等我学会了自定义控件,一定要写一篇系列出来,方便后来者能更...
  • 在项目中增加几个用户自定义控件,想及时在窗口Form上添加时却发现工具箱根本就没有这些用户自定义控件,研究了很久,发现有两个解决办法。通过以下两种方法操作后,再重新编译,在工具箱上就会出现用户自定义控件了...
  • 接上文:编写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,475
精华内容 132,190
关键字:

自定义控件