自定义控件<笔记>

自定义View

自定义View分为三大类,第一种是自定义 View,第二种是自定义 ViewGroup,第三种是自定义组合控件。其中自定义View又分为继承系统控件(比如TextView)和继承View两种。自定义ViewGroup也分为继承ViewGroup 和继承系统特定的 ViewGroup(比如 RelativeLayout)

View的继承关系:

View 的部分继承关系,在这张图我们看到ViewGroup、TextView 等控件继承自 View,LinearLayout、RelativeLayout 等控件继承ViewGroup,TableLayout、RadioGroup等控件继承自LinearLayout,EditText、Button等控件继承自TextView,等等

TIM图片20181207151442.png

坐标系

Android坐标系
在Android中,将屏幕左上角的顶点作为Android坐标系的原点,这个原点向右是X轴正方向,向下是Y轴正方向,另外在触控事件中,使用getRawX()和getRawY()方法获得的坐标也是Android坐标系的坐标。

TIM图片20181207152002.png

View坐标系
除了Android坐标系,还有一个坐标系:View坐标系,它与Android坐标系并不冲突,两者是共同存在的,它们一起来帮助开发者更好地控制View。


TIM图片20181207152102.png

View的执行顺序

https://blog.csdn.net/oqihaogongyuan/article/details/50921560

init->onFinishInflate->onMeasure..->onSizeChanged->onLayout->onDraw()

OnTouchListener,onTouchEvent,onClickListener执行顺序:
首先执行OnTouchListener,之后为onTouchEvent,最后才执行onClickListener内的方法

其中onMeasure和onLayout会被多次调用.

1、init() 构造方法,这个不做解释,铁定第一个被调用。

作用:传入Context

2、onFinishInflate() 当View中所有的子控件均被映射成xml后触发

3、onMeasure() 在View放置到父容器时调用

作用:测量View的大小,也可以通过下面方式,修改View的大小

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(100,100);
}
也可以通过以下方式获取控件的宽和高

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
   int height = MeasureSpec.getSize(heightMeasureSpec));
}

4、onSizeChanged() 在控件大小发生改变时调用。所以这里初始化会被调用一次

作用:获取控件的宽和高度

5,判断ListView的第一个item是否完全显示

ListView.getChildCount()
返回的是这个listView当前显示出来的item数量,也就是在屏幕上可以看到的数量。

ListView.getChildAt(int pos)
返回的是这个listView当前显示出来的item中的第pos项,也就是说索引是从显示出来的第一项开始计算的,如果屏幕上方有n个item看不见,那么这个函数的返回值就是n

ListView.getFirstVisiblePosition()
返回值是当前可以看到的第一个item,在所有item中(包括看不到的)的位置。

View.getTop()
返回值是这个View相对于它的父控件的top值,对于item而言,如果item只有一半显示出来(下半部分),那么返回值应该是 - 这个item的高度

8,getRawX(),getRawY(),getX(),getY(),getScrollX(),getScrollY()区别
1)、MotionEvent 中的getRawX(),getRawY(),getX(),getY()

getRawX(),getRawY():触摸点相对于屏幕的位置 (像素);
getX(),getY():触摸点相对于父View的位置(像素);

2)、View中的getScrollX(),getScrollY()
getScrollX()与getScrollY()的值由调用View的scrollTo(int x, int y)或者scrollBy(int x, int y)产生
其中scrollTo是将View中的内容移动到指定的坐标x、y处,此x、y是相对于View的左上角,而不是屏幕的左上角
scrollBy(int x, int y)则是改变View中的相对位置,参数x、y为距离上一次的相对位置。
x > 0,从右向左滑动,反之从左向右滑动;y > 0,从下向上滑动,反之从上向下滑动;

getScrollX(): 滑动后的位置相对于原始位置横向偏移量,向左滑动 getScrollX() > 0,反之小于0;
getScrollY():滑动后的位置相对于原始位置纵向偏移量,向上滑动getScrollY() > 0,反之小于0;

31521CF0-BBED-4913-9EA8-A6B504F62A01.png

7、onDraw() 最后就是绘制图形了

draw绘制基础

https://www.cnblogs.com/yongdaimi/p/8021965.html
https://blog.csdn.net/struggle323/article/details/50818320

自定义tab

TIM图片20181207160424.png

调用:

FragmentManager manager = getSupportFragmentManager();
 CommFragmentPagerAdapter fragmentPagerAdapter = new CommFragmentPagerAdapter(manager, mList);

        vpSigninList.setAdapter(fragmentPagerAdapter);
        vpiSigninTab.setViewPager(vpSigninList);
        vpiSigninTab.setDatas(mTabData);
        vpSigninList.setCurrentItem(0);

xml文件

 <com.fecmobile.sigemo.common.cusview.ViewPagerIndicator
            android:id="@+id/id_stickynavlayout_indicator"
            android:layout_width="match_parent"
            android:layout_height="@dimen/height_32"
            android:layout_marginLeft="@dimen/margin_10"
            android:layout_marginRight="@dimen/margin_10"
            android:layout_marginTop="@dimen/margin_10">
        </com.fecmobile.sigemo.common.cusview.ViewPagerIndicator>
public class ViewPagerIndicator extends LinearLayout {
    private ViewPager mViewPager;

    private int width;
    private int height;
    private int visibleItemCount = 4;
    private int itemCount = 4;

    //绘制框框
    private Paint paint;
    private Paint paint2;
    private float mWidth = 0;
    private float mHeight = 0;
    private float mLeft = 0;
    private float mTop = 0;
    private float radiusX = 19;
    private float radiusY = 19;
    private int mPadding = 0;

    private List<String> mDatas;
    private boolean isSetData = false;
    private Context context;
    private int currentPosition;
    private boolean isAutoSelect = false;//判断是否进行切换
    private float rebounceOffset;
    private float mTranslationX;

    private boolean isflag = true;

    private TableClickListener tableClickListener;
    private LableClickListener lableClickListener;//是否拦截点击indicator

    public ViewPagerIndicator(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public ViewPagerIndicator(Context context,AttributeSet attrs) {
        super(context,attrs);
        this.context = context;
        init();
    }

    public ViewPagerIndicator(Context context,AttributeSet attrs,int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        this.context = context;
        init();
    }

    private void init() {
        radiusX = DensityUtils.dp2px(context,radiusX);
        radiusY = DensityUtils.dp2px(context,radiusY);
        L.d("画init" + radiusX + "," + radiusY);
        this.setBackgroundDrawable(getResources().getDrawable(R.drawable.circle_tab));
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.color_9a));
        paint.setAntiAlias(true);

        paint2 = new Paint();
        paint2.setStyle(Paint.Style.STROKE);
        paint2.setStrokeWidth(2.0f);
        paint2.setColor(getResources().getColor(R.color.color_ca));
        paint2.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        L.d("画onMeasure" + isSetData + "," + widthMeasureSpec + "," + heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        mWidth = width / visibleItemCount;
        mHeight = height;
    }

    private int currentWidthMeasureSpec;
    private int currentHeightMeasureSpec;

    @Override
    protected void onSizeChanged(int w,int h,int oldw,int oldh) {
        super.onSizeChanged(w,h,oldw,oldh);
        L.d("画onSizeChanged" + isSetData + "," + w + "," + h + "," + oldw + "," + oldh);
        if (isSetData) {
            isSetData = false;
            this.removeAllViews();
            //添加TextView
            for (int i = 0; i < mDatas.size(); i++) {
                TextView tv = new TextView(context);
                tv.setPadding(mPadding,mPadding,mPadding,mPadding);
                // tv.setBackgroundResource(R.drawable.circle_background_tab);
                tv.setText(mDatas.get(i));
                LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
                lp.width = width / visibleItemCount;
                lp.height = height;
                tv.setGravity(Gravity.CENTER);
                tv.setTextColor(getResources().getColor(R.color.pure_white));
                tv.setTextSize(TypedValue.COMPLEX_UNIT_SP,14);

                tv.setLayoutParams(lp);
                final int finalI = i;
                tv.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        boolean isIntercept = false;
                        if (lableClickListener != null) {
                            isIntercept = lableClickListener.onItemClick(finalI);
                        }
                        if (mViewPager != null && !isIntercept) {
                            mViewPager.setCurrentItem(finalI);
                        }
                    }
                });
                addView(tv);
            }
            setTitleColor();
        }
        measure(2,2);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    }

    @Override
    protected void onLayout(boolean changed,int l,int t,int r,int b) {
        super.onLayout(changed,l,t,r,b);
        L.d("画onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        L.d("画" + (mLeft + mPadding) + "," + (mTop + mPadding) + "," + (mLeft + mWidth - mPadding) + "," + (mTop + mHeight - mPadding) + "," + radiusX + "," + radiusY + "," +
            paint);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //drawRoundRect需要的最低API是21
            canvas.drawRoundRect(mLeft + mPadding,mTop + mPadding,mLeft + mWidth - mPadding,mTop + mHeight - mPadding,radiusX,radiusY,paint);
        } else {
            canvas.drawRoundRect(new RectF(mLeft + mPadding,mTop + mPadding,mLeft + mWidth - mPadding,mTop + mHeight - mPadding),radiusX,radiusX,paint);
            //            canvas.drawRect(mLeft + mPadding, mTop + mPadding, mLeft + mWidth - mPadding, mTop + mHeight - mPadding, paint);
        }

        for (int i = 0; i < visibleItemCount - 1; i++) {
            if (i != -1) {
                //确定外切矩形范围
                RectF rectF = new RectF(0,0,mHeight,mHeight);
                //使rectf所确定的矩形向右偏移100像素,向下偏移20像素,170 ,64
                rectF.offset((i * mWidth + (mWidth - mHeight)),0);
                //绘制圆弧,不含圆心
                canvas.drawArc(rectF,-90,180,false,paint2);
            }
        }
    }

    public void setTableClickListener(TableClickListener tableClickListener) {
        this.tableClickListener = tableClickListener;
    }

    public void setLableClickListener(LableClickListener lableClickListener) {
        this.lableClickListener = lableClickListener;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        //ogUtil.m();
        //        canvas.translate(mTranslationX, getHeight() - 2);
        super.dispatchDraw(canvas);
    }

    public void setViewPager(ViewPager viewpager,int position) {
        L.d("画setViewPager" + position);
        this.mViewPager = viewpager;
        this.currentPosition = position;
        if (mViewPager != null) {
            viewpager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int position,float positionOffset,int positionOffsetPixels) {

                    L.d("画圆" + isAutoSelect + "," + position + "," + positionOffset + "," + positionOffsetPixels);
                    //当移动的是最左边item
                    if (isAutoSelect && currentPosition == 0) {
                        //滑动手松开时,让最左边(即第一个)item滑动到左边缘位置
                        if (positionOffset > rebounceOffset / 2) {
                            mLeft = (position + (positionOffset - rebounceOffset / 2) * 2) * mWidth;
                        } else if (positionOffset > rebounceOffset / 3 && positionOffset < rebounceOffset / 2) {
                            //让最左边(即第一个)item 向右回弹一部分距离
                            mLeft = (position + (rebounceOffset / 2) - positionOffset) * mWidth * 6 / 12;
                        } else {
                            //让最左边(即最后一个)item 向左回弹到边缘位置
                            mLeft = (position + positionOffset) * mWidth * 6 / 12;
                        }
                        invalidate();
                    } else if (isAutoSelect && currentPosition == itemCount - 1) {
                        //当移动的是最右边(即最后一个)item

                        //滑动手松开时,让最右边(即最后一个)item滑动到右边缘位置
                        if (positionOffset >= rebounceOffset && positionOffset < (1 - (1 - rebounceOffset) / 2)) {
                            //
                            mLeft = (position + positionOffset / (1 - (1 - rebounceOffset) / 2)) * mWidth;
                            //当item数大于visibleItem可见数,本控件(本质LinearLayout)才滚动
                            if (visibleItemCount < itemCount) {
                                scrollTo((int)(mWidth * positionOffset / (1 - (1 - rebounceOffset) / 2) + (position - visibleItemCount + 1) * mWidth),0);
                            }
                            if ((mLeft + mWidth) > (getChildCount() * mWidth)) {
                                //当(mLeft + mWidth)大于最边缘的宽度时,设置
                                mLeft = (itemCount - 1) * mWidth;
                            }
                        } else if (positionOffset > (1 - (1 - rebounceOffset) / 2) && positionOffset < (1 - (1 - rebounceOffset) / 4)) {
                            //让最右边(即最后一个)item 向左回弹一部分距离

                            //当item数大于visibleItem可见数,且本控件未滚动到指定位置,则设置控件滚动到指定位置
                            if (visibleItemCount < itemCount && getScrollX() != (itemCount - visibleItemCount) * mWidth) {
                                scrollTo((int)((itemCount - visibleItemCount) * mWidth),0);
                            }
                            mLeft = (position + 1) * mWidth - (positionOffset - (1 - (1 - rebounceOffset) / 2)) * mWidth * 7 / 12;
                        } else {
                            //让最右边(即最后一个)item 向右回弹到边缘位置

                            //因为onPageScrolled 最后positionOffset会变成0,所以这里需要判断一下
                            //当positionOffset = 0 时,设置mLeft位置
                            if (positionOffset != 0) {
                                mLeft = (position + 1) * mWidth - (1.0f - positionOffset) * mWidth * 7 / 12;
                                if (mLeft > (itemCount - 1) * mWidth) {
                                    mLeft = (itemCount - 1) * mWidth;
                                }
                            } else {
                                mLeft = (itemCount - 1) * mWidth;
                            }
                        }
                        //invalidate();
                        scroll(position,positionOffset);
                    } else {
                        //当移动的是中间item
                        scrollTo(position,positionOffset);
                        rebounceOffset = positionOffset;
                    }
                    setTitleColor();
                }

                @Override
                public void onPageSelected(int position) {
                    currentPosition = position;
                    if (tableClickListener != null) {
                        tableClickListener.onItemClick(position);
                    }
                }

                @Override
                public void onPageScrollStateChanged(int state) {
                    if (state == 2) {
                        //当state = 2时,表示手松开,viewpager开启自动滑动
                        isAutoSelect = true;
                    }
                    if (state == 0) {
                        //当state = 0时,表示viewpager滑动停止
                        isAutoSelect = false;
                    }
                }
            });
        }
    }

    public void setViewPager(ViewPager viewpager) {

        setViewPager(viewpager,0);
    }

    /**
     * 正常滑动
     */
    private void scrollTo(int position,float positionOffset) {
        //item数量大于可见item,linearlayout才滑动
        if (visibleItemCount < itemCount) {
            if (positionOffset > 0 && position > (visibleItemCount - 2)) {
                this.scrollTo((int)(mWidth * positionOffset + (position - visibleItemCount + 1) * mWidth),0);
            }
        }
        mLeft = (position + positionOffset) * mWidth;
        invalidate();
    }

    /**
     * 设置字体颜色
     */
    private void setTitleColor() {
        if (getChildCount() > 0) {
            for (int i = 0; i < getChildCount(); i++) {
                if (i == currentPosition) {
                    ((TextView)getChildAt(currentPosition)).setTextColor(getResources().getColor(R.color.pure_white));
                } else {
                    ((TextView)getChildAt(i)).setTextColor(getResources().getColor(R.color.color_9a));
                }
            }
        }
    }

    /**
     * 设置内容数据
     */
    public void setDatas(List<String> mDatas) {

        this.isSetData = true;
        this.mDatas = mDatas;
        this.itemCount = mDatas.size();
        if (itemCount < visibleItemCount) {
            visibleItemCount = itemCount;
        }
     
    }

    public void scroll(int position,float offset) {
        /**
         * <pre>
         *  0-1:position=0 ;1-0:postion=0;
         * </pre>
         */
        mTranslationX = getWidth() / itemCount * (position + offset);
        invalidate();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容