Android自定义view

Android自定义View注意事项,初级安卓开发面试题 4.注意padding,让view支持padding,在onDraw方法中获取padding 5.注意滑动冲突,解决滑动冲突 6.尽量不要使用handl er,因为里边已有post系列方法,不需要再用handler 继承View重写onDraw方法 主要用于实现不规则的效果,即这种效果不方便通过布局的组合方式来实现。相当于就是得自己“画”了。采用这种方式需要自己支持wrap_content,padding也需要自己处理 继承ViewGroup派生特殊的Layout 主要用于实现自定义的布局,看起来很像几种V 阅读详情

Android、View视图与坐标系
View的滑动和属性动画
从源码解析View的事件分发机制
View的工作流程
Android自定义view

继承系统控件的自定义View

这种自定义View在系统控件的基础上进行拓展,一般是添加新的功能或者修改显示的效果,一般情况下我们在onDraw()方法中进行处理。这里举一个简单的例子:

public class InvalidTextView extends TextView {
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public InvalidTextView(Context context) {
        super(context);
        initDraw();
    }

    public InvalidTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initDraw();
    }

    public InvalidTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initDraw();
    }

    private void initDraw() {
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth((float) 1.5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        canvas.drawLine(0, height / 2, width, height / 2, mPaint);
    }
}

这个自定义View继承TextView,并且在onDraw()方法中画了一条红色的横线,接下来在布局中引用这个InvalidTextView:

<com.InvalidTextView
    android:id="@+id/iv_text"
    android:layout_width="200dp"
    android:layout_height="100dp"
    android:background="@android:color/holo_blue_light"
    android:gravity="center"
    android:textSize="16sp"
    android:layout_centerHorizontal="true" />

继承View的自定义View

与上面的继承系统控件的自定义View不同,继承View的自定义View实现起来要稍微复杂一些,不只是要实现onDraw()方法,而且在实现过程中还要考虑到wrap_content属性以及padding属性的设置;为了方便配置自己的自定义View还会对外提供自定义的属性,另外如果要改变触控的逻辑,还要重写onTouchEvent()等触控事件的方法。

简单实现继承View的自定义View

按照上面的例子我们再写一个RectView类继承View来画一个正方形:

public class RectView extends View {
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int mColor=Color.RED;
    public RectView(Context context) {
        super(context);
        initDraw();
    }

    public RectView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initDraw();
    }

    public RectView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initDraw();
    }

    private void initDraw() {
        mPaint.setColor(mColor);
        mPaint.setStrokeWidth((float) 1.5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        canvas.drawRect(0, 0, width, height, mPaint);
    }
}

在布局中引用RectView:

<com.RectView
      android:id="@+id/rv_rect"
      android:layout_width="200dp"
      android:layout_height="200dp"
      android:layout_below="@id/iv_text"
      android:layout_marginTop="50dp"
      android:layout_centerHorizontal="true"/>

对padding属性进行处理

如果我在布局文件中设置pading属性,发现没有任何的作用,看来还得对padding属性进行处理,只需要在onDraw()方法中稍加修改就可以了,在绘制正方形的时候考虑到padding属性就可以了:

    @Override
    protected void onDraw(Canvas canvas) {
       super.onDraw(canvas);
       int paddingLeft=getPaddingLeft();
       int paddingRight=getPaddingRight();
       int paddingTop=getPaddingTop();
       int paddingBottom=getPaddingBottom();
       int width = getWidth()-paddingLeft-paddingRight;
       int height = getHeight()-paddingTop-paddingBottom;
       canvas.drawRect(0+paddingLeft, 0+paddingTop, width+paddingRight, height+paddingBottom, mPaint);
    }

修改布局文件加入padding属性:

<com.RectView
    android:id="@+id/rv_rect"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_below="@id/iv_text"
    android:layout_marginTop="50dp"
    android:layout_centerHorizontal="true"
    android:padding="10dp"/>

对wrap_content属性进行处理

修改布局文件,发现RectView的宽度分别为wrap_content和match_parent效果都是一样的。从View的源码上分析:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

对于这种情况需要我们在onMeasure()方法中指定一个默认的宽和高,在设置wrap_content属性时设置此默认的宽和高就可以了:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
      int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
      int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
      int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
      if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
          setMeasuredDimension(400,400);
      }else if(widthSpecMode==MeasureSpec.AT_MOST){
          setMeasuredDimension(400,heightSpecSize);
      }else if(heightSpecMode==MeasureSpec.AT_MOST){
          setMeasuredDimension(widthSpecSize,400);
      }
    }

自定义属性

android系统的控件以android开头的比如android:layout_width,这些都是系统自带的属性,为了方便配置RectView的属性,我们也可以自定义属性,首先在values目录下创建 attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RectView">
        <attr name="rect_color" format="color" />
    </declare-styleable>
</resources>

这个配置文件定义了名为RectView的自定义属性组合,我们定义了rect_color属性,它的格式为color,接下来在RectView的构造函数中解析自定义属性的值:

    public RectView(Context context, AttributeSet attrs) {
       super(context, attrs);
       TypedArray mTypedArray=context.obtainStyledAttributes(attrs,R.styleable.RectView);
       //提取RectView属性集合的rect_color属性,如果没设置默认值为Color.RED
       mColor=mTypedArray.getColor(R.styleable.RectView_rect_color,Color.RED);
       //获取资源后要及时回收
       mTypedArray.recycle();
       initDraw();
   }

在布局里使用

<com.example.liuwangshu.mooncustomview.RectView
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:id="@+id/rv_rect"
      android:layout_width="wrap_content"
      android:layout_height="200dp"
      android:layout_below="@id/iv_text"
      android:layout_marginTop="50dp"
      android:layout_centerHorizontal="true"
      android:padding="10dp"
      app:rect_color="@android:color/holo_blue_light" />

使用自定义属性需要添加schemas: xmlns:app=”http://schemas.android.com/apk/res-auto",其中app是 我们自定义的名字,最后我们配置新定义的app:rect_color属性为android:color/holo_blue_light

自定义组合控件

自定义组合控件就是多个控件组合起来成为一个新的控件,主要用来解决多次重复的使用同一类型的布局。

组合控件的xml布局

我们现在就自定义一个顶部的标题栏,当然实现标题栏有很多的方法,我们来看看自定义组合控件如何去实现。首先我们先定义我们组合控件的布局(view_customtitle.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_titlebar_rootlayout"
    android:layout_width="fill_parent"
    android:layout_height="45dp"
    >

    <ImageView
        android:id="@+id/iv_titlebar_left"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:layout_centerInParent="true"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:src="@drawable/ico_return"
       />
    <TextView
        android:id="@+id/tv_titlebar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:maxEms="11"
        android:singleLine="true"
        android:ellipsize="end"
        android:textStyle="bold"/>
    <ImageView
        android:id="@+id/iv_titlebar_right"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignParentRight="true"
        android:layout_centerInParent="true"
        android:src="@drawable/title_right"
        android:gravity="center"
        android:padding="15dp"
        />
    </RelativeLayout>

很简单的布局,左右边各一个图标,中间是标题文字。

组合控件的Java代码

接下来我们写java代码,因为我们的组合控件整体布局是RelativeLayout,所以我们的组合控件要继承RelativeLayout:

public class TitleBar extends RelativeLayout {
    private ImageView iv_titlebar_left;
    private ImageView iv_titlebar_right;
    private TextView tv_titlebar_title;
    private RelativeLayout layout_titlebar_rootlayout;
    private int mColor= Color.BLUE;
    private int mTextColor= Color.WHITE;
    public TitleBar(Context context) {
        super(context);
        initView(context);
    }

    public TitleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public TitleBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    public void initView(Context context){
        LayoutInflater.from(context).inflate(R.layout.view_customtitle, this, true);
        iv_titlebar_left= (ImageView) findViewById(R.id.iv_titlebar_left);
        iv_titlebar_right= (ImageView) findViewById(R.id.iv_titlebar_right);
        tv_titlebar_title= (TextView) findViewById(R.id.tv_titlebar_title);
        layout_titlebar_rootlayout= (RelativeLayout) findViewById(R.id.layout_titlebar_rootlayout);
        //设置背景颜色
        layout_titlebar_rootlayout.setBackgroundColor(mColor);
        //设置标题文字颜色
        tv_titlebar_title.setTextColor(mTextColor);
    }
    public void setTitle(String titlename){
        if(!TextUtils.isEmpty(titlename)) {
            tv_titlebar_title.setText(titlename);
        }
    }

    public void setLeftListener(OnClickListener onClickListener){
        iv_titlebar_left.setOnClickListener(onClickListener);
    }
    public void setRightListener(OnClickListener onClickListener){
        iv_titlebar_right.setOnClickListener(onClickListener);
    }
}

重写了三个构造方法并在构造方法中加载布局文件,对外提供了三个方法,分别用来设置标题的名字,和左右按钮的点击事件。

自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TitleBar">
        <attr name="title_text_color" format="color" />
        <attr name="title_bg" format="color" />
        <attr name="title_text" format="string" />
    </declare-styleable>
</resources>

我们定义了三个属性,分别用来设置顶部标题栏的背景颜色、标题文字颜色和标题文字。为了引入自定义属性我们需要在TitleBar的构造函数中解析自定义属性的值:

public TitleBar(Context context, AttributeSet attrs) {
       super(context, attrs);
       TypedArray mTypedArray=context.obtainStyledAttributes(attrs,R.styleable.TitleBar);
       mColor=mTypedArray.getColor(R.styleable.TitleBar_title_bg,Color.BLUE);
       mTextColor=mTypedArray.getColor(R.styleable.TitleBar_title_text_color, Color.WHITE);
       titlename=mTypedArray.getString(R.styleable.TitleBar_title_text);
       //获取资源后要及时回收
       mTypedArray.recycle();
       initView(context);
   }

xml中引用组合控件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.TitleBar
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        app:title_text="自定义组合控件"
        app:title_bg="@android:color/holo_orange_dark"
        app:title_text_color="@android:color/holo_blue_dark" />
</LinearLayout>

调用组合控件

在主界面调用我们自定义的TitleBar,并设置了左右两遍按钮的点击事件:

public class MainActivity extends Activity {
    private TitleBar mTitleBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTitleBar= (TitleBar) this.findViewById(R.id.title);
//      mTitleBar.setTitle("自定义组合控件");

        mTitleBar.setLeftListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点击左键", Toast.LENGTH_SHORT).show();
            }
        });

        mTitleBar.setRightListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点击右键", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

自定义ViewGroup

以下的例子是一个自定义的ViewGroup,左右滑动切换不同的页面,类似一个特别简化的ViewPager,这篇文章会涉及到这个系列的很多文章的内容比如View的measure、layout和draw流程,view的滑动等等。

对wrap_content属性进行处理

public class HorizontalView extends ViewGroup {
    public HorizontalView(Context context) {
        super(context);
    }
    
    public HorizontalView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @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);
    
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //如果没有子元素,就设置宽高都为0(简化处理)
        if (getChildCount() == 0) { 
            setMeasuredDimension(0, 0);
        }
        //宽和高都是AT_MOST,则设置宽度所有子元素的宽度的和;高度设置为第一个元素的高度;
        else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth * getChildCount(), childHeight);
        }
        //如果宽度是wrap_content,则宽度为所有子元素的宽度的和
        else if (widthMode == MeasureSpec.AT_MOST) {
            int childWidth = getChildAt(0).getMeasuredWidth();
            setMeasuredDimension(childWidth * getChildCount(), heightSize);
        }
        //如果高度是wrap_content,则高度为第一个子元素的高度
        else if (heightMode == MeasureSpec.AT_MOST) {
            int childHeight = getChildAt(0).getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight);
        }
      
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    }
}

这里如果没有子元素时采用了简化的写法直接将宽和高直接设置为0,正常的话我们应该根据LayoutParams中的宽和高来做相应的处理,另外我们在测量时没有考虑它的padding和子元素的margin。

实现onLayout

接下来我们实现onLayout,来布局子元素,因为每一种布局方式子View的布局都是不同的,所以这个是ViewGroup唯一一个抽象方法,需要我们自己去实现:

public class HorizontalView extends ViewGroup {
    //... 省略构造方法代码和onMeasure的代码
   
    @Override
       protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0;
        View child;
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                int width = child.getMeasuredWidth();
                childWidth = width; 
                child.layout(left, 0, left + width, child.getMeasuredHeight());
                left += width;
            }
        }
    }

遍历所有的子元素,如果子元素不是GONE,则调用子元素的layout方法将其放置到合适的位置上,相当于默认第一个子元素占满了屏幕,后面的子元素就是在第一个屏幕后面紧挨着和屏幕一样大小的后续元素,所以left是一直累加的,top保持0,bottom保持第一个元素的高度,right就是left+元素的宽度,同样这里没有处理自身的pading以及子元素的margin。

处理滑动冲突

这个自定义ViewGroup是水平滑动,如果里面是ListView,则ListView是垂直滑动,如果我们检测到的滑动方向是水平的话,就让父View拦截用来进行View的滑动切换 :

public class HorizontalView extends ViewGroup {
    private int lastInterceptX;
    private int lastInterceptY;
    private int lastX;
    private int lastY;
    //... 省略了构造函数的代码
  
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastInterceptX; 
                int deltaY = y - lastInterceptY; 
                //用户想水平滑动的,所以拦截
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) { 
                    intercept = true; 
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        lastX = x;
        lastY = y;
        lastInterceptX = x; 
        lastInterceptY = y;
        return intercept;
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
    //... 省略了onMeasure和onLayout的代码
}

弹性滑动到其他页面

这里就会进入onTouchEvent事件,然后我们需要进行滑动切换页面,这里需要用到Scroller。

public class HorizontalView extends ViewGroup {
    //... 省略构造函数,init方法,onInterceptTouchEvent
    int lastInterceptX;
    int lastInterceptY;
    int lastX;
    int lastY;
    int currentIndex = 0; //当前子元素
    int childWidth = 0; 
    private Scroller scroller;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastX; //跟随手指滑动
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP: 
             //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth;
                //滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                }
                smoothScrollTo(currentIndex * childWidth, 0);
                break;
        }
        lastX = x;
        lastY = y;
        return super.onTouchEvent(event);
    }
    //...省略onMeasure方法
     @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }
    //弹性滑动到指定位置
    public void smoothScrollTo(int destX, int destY) {
        scroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(),
        destY - getScrollY(), 1000); 
        invalidate();
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0; 
        View child;
        //遍历布局子元素
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            int width = child.getMeasuredWidth();
            //赋值为子元素的宽度
            childWidth = width; 
            child.layout(left, 0, left + width, child.getMeasuredHeight());
            left += width;
        }
    }
}

快速滑动到其他页面

我们不只滑动超过一半才切换到上/下一个页面,如果滑动速度很快的话,我们也可以判定为用户想要滑动到其他页面,这样的体验也是好的。 这部分也是在onTouchEvent中的ACTION_UP部分:
这里又需要用到VelocityTracker,它用来测试滑动速度的。使用方法也很简单,首先在构造函数中进行初始化,也就是前面的init方法中增加一条语句:

...
 private VelocityTracker tracker;    
 ...
 public void init() {
       scroller = new Scroller(getContext());
       tracker=VelocityTracker.obtain();
   }
 ...

接着改写onTouchEvent部分:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
            ...
          case MotionEvent.ACTION_UP:
              //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth; 
                //必须滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                }
                else {
                //调用该方法计算1000ms内滑动的平均速度   
                 tracker.computeCurrentVelocity(1000);
                    float xV = tracker.getXVelocity(); //获取到水平方向上的速度
                    //如果速度的绝对值大于50的话,就认为是快速滑动,就执行切换页面
                    if (Math.abs(xV) > 50) { 
                    //大于0切换上一个页面
                        if (xV > 0) { 
                            currentIndex--;
                    //小于0切换到下一个页面
                        } else { 
                            currentIndex++;
                        }
                    }
                }
                currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ?
                getChildCount() - 1 : currentIndex;
                smoothScrollTo(currentIndex * childWidth, 0);
                //重置速度计算器
                tracker.clear();
                break;
             }

再次触摸屏幕阻止页面继续滑动

当我们快速向左滑动切换到下一个页面的情况,在手指释放以后,页面会弹性滑动到下一个页面,可能需要一秒才完成滑动,这个时间内,我们再次触摸屏幕,希望能拦截这次滑动,然后再次去操作页面。
要实现在弹性滑动过程中再次触摸拦截,肯定要在onInterceptTouchEvent中的ACTION_DOWN中去判断,如果在ACTION_DOWN的时候,scroller还没有完成,说明上一次的滑动还正在进行中,则直接中断scroller:

...
 @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
     
        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: 
                intercept = false;
               
               //如果动画还没有执行完成,则打断
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:          
                int deltaX = x - lastInterceptX;
                int deltaY = y - lastInterceptY;
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) { 
                    intercept = true;
                } else {
                    intercept = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        //因为DOWN返回false,所以onTouchEvent中无法获取DOWN事件,所以这里要负责设置lastX,lastY
        lastX = x;
        lastY = y;
        lastInterceptX = x;
        lastInterceptY = y;
        return intercept;
    }
...

应用HorizontalView

首先我们在主布局中引用HorizontalView,它作为父容器,里面有两个ListView:

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context="com.example.liuwangshu.mooncustomviewgroup.MainActivity">
    <com.example.liuwangshu.mooncustomviewgroup.HorizontalView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
       <ListView
        android:id="@+id/lv_one"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
        <ListView
            android:id="@+id/lv_two"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></ListView>
    </com.example.liuwangshu.mooncustomviewgroup.HorizontalView>
</RelativeLayout>

以上内容摘自《Android进阶之光》

安卓面试之自定义View流程及原理 自定义View原理学习博客整理 阅读详情

相关推荐

安卓自定义view

自定义view

qq_33386162的博客 830

android-自定义控件(组合控件)出现的问题

1. 无法识别attrs.xml文件里自己定义的属性 在attrs.xml文件下已经定义了属性,如代码片段2,但是尝试用代码片段1 //代码片段1 TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MRadio,0,0); String mTitle = typedArray.getString(R.s...

Between098的博客 459

2024最新Typora配置指南:从安装到打造学术写作环境(含LaTeX/参考文献解决方案)

本文提供2024年最新Typora配置指南,详细讲解如何从安装开始,结合Pandoc、Zotero和LaTeX,打造高效的学术写作环境。指南涵盖Typora深度定制、学术主题配置、参考文献自动管理以及一键生成出版级PDF的完整解决方案,旨在帮助科研人员和学生实现内容与格式分离,大幅提升论文写作效率。

weixin_29258297的博客 213

Android自定义view:自定义属性 TypedArray

定义: <declare-styleable name="MyView"> <attr name="vColor" format="color" /> <attr name="vDrawable" format="color|reference" /> <attr name="vDimen" format="dimension" /> <attr name="vReference" format="reference" /

火星男孩的分享空间 4318

超酷炫,Python实现交通数据可视化!

此时输出的栅格化参数params的内容存储了栅格坐标系的原点坐标(slon、slat)、单个栅格的经纬度长宽 (deltalon、deltalat)、栅格的旋转角度(theta)、栅格的形状(method参数,其值可以是方形rect、三角形tri和六边形hexa)以及栅格的大小(gridsize参数,单位为米)。Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。今天分享一次Python交通数据分析与可视化的实战!

m0_59236127的博客 1697

FANUC机器人电弧跟踪功能深度解析:从电流采样原理到ROBOGUIDE仿真避坑指南

本文深度解析FANUC机器人电弧跟踪技术,从电流采样原理到ROBOGUIDE仿真避坑指南,详细介绍了电流波形分析、实时补偿机制及现场调试技巧。针对工业焊接中的常见问题,提供从底层原理到高阶应用的解决方案,帮助工程师优化自动化焊接质量。

weixin_31785177的博客 553

Android自定义属性TypedArray详解

大家好,我是程序员双木L,后续会发专题类的文章,这是自定义控件的第一篇,之后也会陆续更新相关的文章,欢迎关注。 自定义属性在自定义控件过程中属于比较常见的操作,我们可以回想一下这样的场景:自定义view的过程中,我们需要在不同的情况下设置不同的文字大小,那么我们是不是就需要提供对外的方法来设置,这样就比较灵活操作。而我们自定义对外的方法,就是我们自定义的属性啦,那我们来分析一下其原理及作用。 下面我们根据例子来进行分析: 1、首先我们需要在res->values目录下新建attrs.xml文件,该文件

龙旋的博客 1336

AI时代的软件质量保证新方法论

3.2.1 质量保证的方法论 质量保证(Quality Assurance, QA)是一种系统化的方法,用于确保产品或服务符合预定的质量标准。在AI时代,软件质量保证的方法论需要适应不断变化的AI技术和应用场景。 3.2.1.1 质量管理体系的构建 首先,构建一个完善

AI天才研究院 1356

Android自定义View

概述 Android开发进阶的必经之路 为什么要自定义View 自定义View的基本方法 自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw(); View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。 测量:onMeasure()决定View的大小; 布局:onLayout()决定View在ViewGroup中的位置; 绘制:onDraw()决定绘制这个View。 自定义控

Rayman的博客 1万+

DFI协议时序

参数名称典型值应用场景关键影响tcmd_lat1-2个时钟周期命令与地址锁存同步地址错误风险tphy_wrlat1-2个时钟周期写操作响应速度写带宽与延迟2-3个时钟周期写操作端到端延迟系统吞吐量1-2个时钟周期连续写命令间隔信号干扰与资源冲突tphy_rdlat1-2个时钟周期读操作响应速度读带宽与延迟trddata_en由CL决定(如0.41 ns)读数据返回时间系统延迟10-20 ps写数据与命令对齐数据丢失风险<1 µs低功耗模式切换。

qq_43134430的博客 5104

uniapp - 热更新提示:WGT安装包中manifest.json文件不存在 | unexpected EOF!报错详细解决,完美搞定WGT安装包中WWW目录下manifest.json不存在问题

uniapp,安卓android,苹果ios,热更新,热更新code:-1202报错,,WGT热更新报错,uniapp热更新wgt出现报错manifest.json文件不存,uniapp安卓苹果软件热更新时出现错误unexpected EOF!如何解决,怎么解决热更新wgt报错,app新版本下载安装报错WGT安装包中manifest.json文件不存在,WGT安装包中WWW目录下manifest.json不存在,uniapp热更新报错安装失败[-1201]:WGT/WGTU文件格式错误,uniapp-wgt

✅ 精选推荐文章 1万+

安卓自定义View

安卓自定义View

eos-i的博客 5250

全国区块链职业技能大赛国赛考题前端功能开发

全国区块链职业技能大赛国赛考题区块链应用前端功能开发

本郡主是喵 1335

安卓Android---自定义view

android的自定义view

Chenbaozeng的博客 2162

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

安卓开发 之小白养成-Android自定义View

自定义View的作用和场景 Android中内置的UI控件和布局⽆无法满⾜足需求的时候,就需要进⾏外观、操作都是⾃自定义的⼀一些控件, 定义View。 自定义View三种方式 继承已有的控件来实现自定义控件。 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。增加新的功能。 2.继承ViewGroup,把多个控件组合成一个自定义控件(组合控件...

qq_34506989的博客 590

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Android开发之自定义View

在自定义View的时候,需要两个构造函数。否则在编译的时候会报异常:Binary XML file line Error inflating class. 原因是:Android在根据xml文件夹创建View对象的时候会调用View的双参构造方法,即public SquareView(Context context, AttributeSetattrs),所以如果没有写全的话就会报错。...

weixin_43858011的博客 1万+

重学Android:自定义View基础(一)

作为一名安卓开发,也被称为大前端,做一个美观的界面,是我们必备的基础技能,可能在开发中我们最常用的是系统自带的View,因为他能满足绝大部分需求,难一点的我们也可以上Github上找个三方库使用,少数情况下会让我们进行自定义View,当然这不代表着我们可以不去掌握其原理,因为它是通往中高级程序员的必经之路,也是大厂面试的热门知识,只有熟练掌握其核心原理,才能让我们在后续的开发中游刃有余。

WriteBug001的博客 1293

Android最全自定义view的入门,面试安卓卡顿

本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。

2401_84544914的博客 372

安卓自定义view(全面)

安卓自定义view 一、view的测量 1.测量的模式: EXACTLY :精确值模式,当把空间的layout_width属性或者layout_height的属性设置为具体的数值时,或者指定为match_parent时候,系统就是使用的EXACTLY 模式; AT_MOST: 最大值模式,当空间的layout_width属性或者layout_height属性指定为wrap_content时候,控件大小跟着控件的内容变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。 UNSPECIFIED: 这

twenty_four_7的博客 3254

安卓自定义view小实例 + MVC 思想

安卓自定义view小实例 + MVC 思想 一、自定义view实例 1.布局文件下添加控件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schem

twenty_four_7的博客 659

深入理解Android之自定义View

自定义View、多线程、网络,被认为是Android开发者必须牢固掌握的最基础的三大基本功。而自定义View又是Android开发中特别重要的一环,很多地方都需要用到自定义View。这篇文章我就梳理一下自定义View的相关知识。 目录 (一)什么是自定义View (二)自定义组合View (三)自定义View的绘制 1. Canvas.drawXXX() Pa...

Fighting_初心的博客 1万+

【安卓开发】自定义View

实现特殊UI效果封装复杂交互逻辑提高View的复用性优化性能(针对特定场景)继承现有View(如Button、TextView等)继承ViewGroup(实现特殊布局)直接继承View(完全自定义)// 在代码中new时调用init();// 在XML布局中使用时调用init();// 解析自定义属性// 带样式时调用init();init();// 初始化画笔等对象try {// 必须回收// 必须实现的构造方法...@Override。

weixin_46896210的博客 1117
上一篇: View的工作流程
下一篇: Android多线程编程
「已注销」
博客等级 码龄9年 13粉丝 15原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值