自定義View
自定義View分為三大類(lèi),第一種是自定義 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等控件繼承自L(fǎng)inearLayout,EditText、Button等控件繼承自TextView,等等
坐標(biāo)系
Android坐標(biāo)系
在A(yíng)ndroid中,將屏幕左上角的頂點(diǎn)作為Android坐標(biāo)系的原點(diǎn),這個(gè)原點(diǎn)向右是X軸正方向,向下是Y軸正方向,另外在觸控事件中,使用getRawX()和getRawY()方法獲得的坐標(biāo)也是Android坐標(biāo)系的坐標(biāo)。
View坐標(biāo)系
除了Android坐標(biāo)系,還有一個(gè)坐標(biāo)系:View坐標(biāo)系,它與Android坐標(biāo)系并不沖突,兩者是共同存在的,它們一起來(lái)幫助開(kāi)發(fā)者更好地控制View。
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會(huì)被多次調(diào)用.
1、init() 構(gòu)造方法,這個(gè)不做解釋?zhuān)F定第一個(gè)被調(diào)用。
作用:傳入Context
2、onFinishInflate() 當(dāng)View中所有的子控件均被映射成xml后觸發(fā)
3、onMeasure() 在View放置到父容器時(shí)調(diào)用
作用:測(cè)量View的大小,也可以通過(guò)下面方式,修改View的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(100,100);
}
也可以通過(guò)以下方式獲取控件的寬和高
@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ā)生改變時(shí)調(diào)用。所以這里初始化會(huì)被調(diào)用一次
作用:獲取控件的寬和高度
5,判斷ListView的第一個(gè)item是否完全顯示
ListView.getChildCount()
返回的是這個(gè)listView當(dāng)前顯示出來(lái)的item數(shù)量,也就是在屏幕上可以看到的數(shù)量。
ListView.getChildAt(int pos)
返回的是這個(gè)listView當(dāng)前顯示出來(lái)的item中的第pos項(xiàng),也就是說(shuō)索引是從顯示出來(lái)的第一項(xiàng)開(kāi)始計(jì)算的,如果屏幕上方有n個(gè)item看不見(jiàn),那么這個(gè)函數(shù)的返回值就是n
ListView.getFirstVisiblePosition()
返回值是當(dāng)前可以看到的第一個(gè)item,在所有item中(包括看不到的)的位置。
View.getTop()
返回值是這個(gè)View相對(duì)于它的父控件的top值,對(duì)于item而言,如果item只有一半顯示出來(lái)(下半部分),那么返回值應(yīng)該是 - 這個(gè)item的高度
8,getRawX(),getRawY(),getX(),getY(),getScrollX(),getScrollY()區(qū)別
1)、MotionEvent 中的getRawX(),getRawY(),getX(),getY()
getRawX(),getRawY():觸摸點(diǎn)相對(duì)于屏幕的位置 (像素);
getX(),getY():觸摸點(diǎn)相對(duì)于父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)容移動(dòng)到指定的坐標(biāo)x、y處,此x、y是相對(duì)于View的左上角,而不是屏幕的左上角
scrollBy(int x, int y)則是改變View中的相對(duì)位置,參數(shù)x、y為距離上一次的相對(duì)位置。
x > 0,從右向左滑動(dòng),反之從左向右滑動(dòng);y > 0,從下向上滑動(dòng),反之從上向下滑動(dòng);
getScrollX(): 滑動(dòng)后的位置相對(duì)于原始位置橫向偏移量,向左滑動(dòng) getScrollX() > 0,反之小于0;
getScrollY():滑動(dòng)后的位置相對(duì)于原始位置縱向偏移量,向上滑動(dòng)getScrollY() > 0,反之小于0;
7、onDraw() 最后就是繪制圖形了
draw繪制基礎(chǔ)
https://www.cnblogs.com/yongdaimi/p/8021965.html
https://blog.csdn.net/struggle323/article/details/50818320
自定義tab
調(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;//判斷是否進(jìn)行切換
private float rebounceOffset;
private float mTranslationX;
private boolean isflag = true;
private TableClickListener tableClickListener;
private LableClickListener lableClickListener;//是否攔截點(diǎn)擊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("畫(huà)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("畫(huà)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("畫(huà)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("畫(huà)onLayout");
}
@Override
protected void onDraw(Canvas canvas) {
L.d("畫(huà)" + (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("畫(huà)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("畫(huà)圓" + isAutoSelect + "," + position + "," + positionOffset + "," + positionOffsetPixels);
//當(dāng)移動(dòng)的是最左邊item
if (isAutoSelect && currentPosition == 0) {
//滑動(dòng)手松開(kāi)時(shí),讓最左邊(即第一個(gè))item滑動(dòng)到左邊緣位置
if (positionOffset > rebounceOffset / 2) {
mLeft = (position + (positionOffset - rebounceOffset / 2) * 2) * mWidth;
} else if (positionOffset > rebounceOffset / 3 && positionOffset < rebounceOffset / 2) {
//讓最左邊(即第一個(gè))item 向右回彈一部分距離
mLeft = (position + (rebounceOffset / 2) - positionOffset) * mWidth * 6 / 12;
} else {
//讓最左邊(即最后一個(gè))item 向左回彈到邊緣位置
mLeft = (position + positionOffset) * mWidth * 6 / 12;
}
invalidate();
} else if (isAutoSelect && currentPosition == itemCount - 1) {
//當(dāng)移動(dòng)的是最右邊(即最后一個(gè))item
//滑動(dòng)手松開(kāi)時(shí),讓最右邊(即最后一個(gè))item滑動(dòng)到右邊緣位置
if (positionOffset >= rebounceOffset && positionOffset < (1 - (1 - rebounceOffset) / 2)) {
//
mLeft = (position + positionOffset / (1 - (1 - rebounceOffset) / 2)) * mWidth;
//當(dāng)item數(shù)大于visibleItem可見(jiàn)數(shù),本控件(本質(zhì)LinearLayout)才滾動(dòng)
if (visibleItemCount < itemCount) {
scrollTo((int)(mWidth * positionOffset / (1 - (1 - rebounceOffset) / 2) + (position - visibleItemCount + 1) * mWidth),0);
}
if ((mLeft + mWidth) > (getChildCount() * mWidth)) {
//當(dāng)(mLeft + mWidth)大于最邊緣的寬度時(shí),設(shè)置
mLeft = (itemCount - 1) * mWidth;
}
} else if (positionOffset > (1 - (1 - rebounceOffset) / 2) && positionOffset < (1 - (1 - rebounceOffset) / 4)) {
//讓最右邊(即最后一個(gè))item 向左回彈一部分距離
//當(dāng)item數(shù)大于visibleItem可見(jiàn)數(shù),且本控件未滾動(dòng)到指定位置,則設(shè)置控件滾動(dòng)到指定位置
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 {
//讓最右邊(即最后一個(gè))item 向右回彈到邊緣位置
//因?yàn)閛nPageScrolled 最后positionOffset會(huì)變成0,所以這里需要判斷一下
//當(dāng)positionOffset = 0 時(shí),設(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 {
//當(dāng)移動(dòng)的是中間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) {
//當(dāng)state = 2時(shí),表示手松開(kāi),viewpager開(kāi)啟自動(dòng)滑動(dòng)
isAutoSelect = true;
}
if (state == 0) {
//當(dāng)state = 0時(shí),表示viewpager滑動(dòng)停止
isAutoSelect = false;
}
}
});
}
}
public void setViewPager(ViewPager viewpager) {
setViewPager(viewpager,0);
}
/**
* 正常滑動(dòng)
*/
private void scrollTo(int position,float positionOffset) {
//item數(shù)量大于可見(jiàn)item,linearlayout才滑動(dòng)
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();
}
}