自定義控件<筆記>

自定義View

自定義View分為三大類,第一種是自定義 View坐桩,第二種是自定義 ViewGroup,第三種是自定義組合控件。其中自定義View又分為繼承系統(tǒng)控件(比如TextView)和繼承View兩種抖拴。自定義ViewGroup也分為繼承ViewGroup 和繼承系統(tǒng)特定的 ViewGroup(比如 RelativeLayout)

View的繼承關(guān)系:

View 的部分繼承關(guān)系,在這張圖我們看到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坐標系并不沖突回论,兩者是共同存在的散罕,它們一起來幫助開發(fā)者更好地控制View。


TIM圖片20181207152102.png

View的執(zhí)行順序

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

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

OnTouchListener傀蓉,onTouchEvent欧漱,onClickListener執(zhí)行順序:
首先執(zhí)行OnTouchListener,之后為onTouchEvent葬燎,最后才執(zhí)行onClickListener內(nèi)的方法

其中onMeasure和onLayout會被多次調(diào)用.

1误甚、init() 構(gòu)造方法,這個不做解釋谱净,鐵定第一個被調(diào)用窑邦。

作用:傳入Context

2、onFinishInflate() 當View中所有的子控件均被映射成xml后觸發(fā)

3壕探、onMeasure() 在View放置到父容器時調(diào)用

作用:測量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() 在控件大小發(fā)生改變時調(diào)用瞧筛。所以這里初始化會被調(diào)用一次

作用:獲取控件的寬和高度

5,判斷ListView的第一個item是否完全顯示

ListView.getChildCount()
返回的是這個listView當前顯示出來的item數(shù)量,也就是在屏幕上可以看到的數(shù)量导盅。

ListView.getChildAt(int pos)
返回的是這個listView當前顯示出來的item中的第pos項较幌,也就是說索引是從顯示出來的第一項開始計算的,如果屏幕上方有n個item看不見白翻,那么這個函數(shù)的返回值就是n

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

View.getTop()
返回值是這個View相對于它的父控件的top值,對于item而言岛琼,如果item只有一半顯示出來(下半部分)底循,那么返回值應(yīng)該是 - 這個item的高度

8,getRawX(),getRawY(),getX(),getY(),getScrollX(),getScrollY()區(qū)別
1)、MotionEvent 中的getRawX(),getRawY(),getX(),getY()

getRawX()衷恭,getRawY():觸摸點相對于屏幕的位置 (像素)此叠;
getX(),getY():觸摸點相對于父View的位置(像素);

2)随珠、View中的getScrollX(),getScrollY()
getScrollX()與getScrollY()的值由調(diào)用View的scrollTo(int x, int y)或者scrollBy(int x, int y)產(chǎn)生
其中scrollTo是將View中的內(nèi)容移動到指定的坐標x灭袁、y處,此x窗看、y是相對于View的左上角茸歧,而不是屏幕的左上角
scrollBy(int x, int y)則是改變View中的相對位置,參數(shù)x显沈、y為距離上一次的相對位置软瞎。
x > 0,從右向左滑動,反之從左向右滑動拉讯;y > 0,從下向上滑動涤浇,反之從上向下滑動;

getScrollX(): 滑動后的位置相對于原始位置橫向偏移量魔慷,向左滑動 getScrollX() > 0只锭,反之小于0;
getScrollY():滑動后的位置相對于原始位置縱向偏移量院尔,向上滑動getScrollY() > 0,反之小于0蜻展;

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

7、onDraw() 最后就是繪制圖形了

draw繪制基礎(chǔ)

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

自定義tab

TIM圖片20181207160424.png

調(diào)用:

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數(shù)大于visibleItem可見數(shù)施逾,本控件(本質(zhì)LinearLayout)才滾動
                            if (visibleItemCount < itemCount) {
                                scrollTo((int)(mWidth * positionOffset / (1 - (1 - rebounceOffset) / 2) + (position - visibleItemCount + 1) * mWidth),0);
                            }
                            if ((mLeft + mWidth) > (getChildCount() * mWidth)) {
                                //當(mLeft + mWidth)大于最邊緣的寬度時,設(shè)置
                                mLeft = (itemCount - 1) * mWidth;
                            }
                        } else if (positionOffset > (1 - (1 - rebounceOffset) / 2) && positionOffset < (1 - (1 - rebounceOffset) / 4)) {
                            //讓最右邊(即最后一個)item 向左回彈一部分距離

                            //當item數(shù)大于visibleItem可見數(shù)例获,且本控件未滾動到指定位置音念,則設(shè)置控件滾動到指定位置
                            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 時躏敢,設(shè)置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數(shù)量大于可見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();
    }

    /**
     * 設(shè)置字體顏色
     */
    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));
                }
            }
        }
    }

    /**
     * 設(shè)置內(nèi)容數(shù)據(jù)
     */
    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();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啼器,一起剝皮案震驚了整個濱河市旬渠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌端壳,老刑警劉巖告丢,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異损谦,居然都是意外死亡岖免,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門照捡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颅湘,“玉大人,你說我怎么就攤上這事栗精〈巢危” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵悲立,是天一觀的道長鹿寨。 經(jīng)常有香客問我,道長薪夕,這世上最難降的妖魔是什么脚草? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮寥殖,結(jié)果婚禮上玩讳,老公的妹妹穿的比我還像新娘。我一直安慰自己嚼贡,他們只是感情好熏纯,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粤策,像睡著了一般樟澜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叮盘,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天秩贰,我揣著相機與錄音,去河邊找鬼柔吼。 笑死毒费,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的愈魏。 我是一名探鬼主播觅玻,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼想际,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了溪厘?” 一聲冷哼從身側(cè)響起胡本,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎畸悬,沒想到半個月后侧甫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蹋宦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年披粟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妆档。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡僻爽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贾惦,到底是詐尸還是另有隱情胸梆,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布须板,位于F島的核電站碰镜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏习瑰。R本人自食惡果不足惜绪颖,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甜奄。 院中可真熱鬧柠横,春花似錦、人聲如沸课兄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烟阐。三九已至搬俊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜒茄,已是汗流浹背唉擂。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留檀葛,地道東北人玩祟。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像屿聋,于是被迫代替她去往敵國和親卵凑。 傳聞我的和親對象是個殘疾皇子庆聘,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容