您的位置:首页 > 移动开发 > Android开发

Android 自定义View

2016-08-09 00:00 363 查看
这篇文章紧接着上面两篇文章(Android事件机制和Android View的工作原理来说明自定义View的一些注意事项)下面通过两个例子来说明自定义view的过程。

CircleView 绘制一个圆形的View

ScrollView绘制一个可以滑动的列表

CircleView

1.Circle可以做到的事情:支持padding属性,支持wrap_content布局模式,在指定的地点绘制一个圆,支持app:color属性,用来指定view的背景颜色,效果图



2.预备知识点

Circle继承自View

为了支持wrap_content需要自己重写onMeasure方法,因为,wrap_content模式下,要测量的时候,父元素传进来的是AT_MOST模式和parentSize,如果不处理就和match_parent模式一样,所以需要设置一个默认的大小,当为wrap_content的时候设置为这个大小

onMeasure的重写方法,需要分解出下面两个值,根据值来确定measure的大小,测量完成之后要用setMeasureDimension方法来设置值的大小
MeasureSpec.getMode
MeasureSpec.getSize


padding属性是和具体的子元素的布局有关,因此在View基类里面没有实现,为了支持这个属性需要自己设置大小(在绘制的时候设置大小)

为了支持自定义属性,需要接收AttributeSet来获得这些属性值,也要声明自己的命名前缀

3.代码实现

3.1CircleView的主文件

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/**
* Created by kisstheraik on 16/8/8.
* Description 一个圆形的view支持大小变化和颜色变化
*/
public class CircleView extends View {

private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
private int color=Color.RED;

public CircleView(Context context){
super(context);

}

public CircleView(Context context,AttributeSet attrs){

this(context,attrs,0);

}

public CircleView(Context context,AttributeSet attributeSet,int defStyle){

super(context,attributeSet,defStyle);

TypedArray list=context.obtainStyledAttributes(attributeSet,R.styleable.CircleView);

color=list.getColor(R.styleable.CircleView_color,Color.RED);//解析出自定义的color属性

//这里需要回收资源
list.recycle();

}
@Override
protected void onDraw(Canvas canvas){

//处理padding

int paddingLeft=getPaddingLeft();
int paddingRight=getPaddingRight();
int paddingTop=getPaddingTop();
int paddingBottom=getPaddingBottom();

int width=getWidth()-paddingLeft-paddingRight;
int height=getHeight()-paddingBottom-paddingTop;
int radius=Math.min(width, height)/2;

paint.setColor(color);

//画出圆
canvas.drawCircle(width / 2 + paddingLeft, height / 2 + paddingTop, radius, paint);

}
//为了支持wrap_content模式,重写measure方法
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int wmode=MeasureSpec.getMode(widthMeasureSpec);
int hmode=MeasureSpec.getSize(heightMeasureSpec);

int wsize=MeasureSpec.getMode(widthMeasureSpec);
int hsize=MeasureSpec.getSize(heightMeasureSpec);
//根据不同的模式设置不同的值
if(wmode==MeasureSpec.AT_MOST&&hmode==MeasureSpec.AT_MOST){
setMeasuredDimension(200,200);
}else if(wmode==MeasureSpec.AT_MOST){

setMeasuredDimension(200,hsize);

}else if(hmode==MeasureSpec.AT_MOST){

setMeasuredDimension(wsize,200);

}

}

public void setColor(int color){

this.color=color;
//可以在代码里面修改view的颜色
invalidate();

}
}

3.2xml布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>

<com.kejian.mike.scrollpage.CircleView

android:id="@+id/mycircle"
android:layout_width="100dp"
android:layout_height="100dp"
app:color="@android:color/holo_green_light"
android:background="#ffffff"

</LinearLayout>

</RelativeLayout>


ScrollView

1.scrollView可以做到的事情,scrollview可以里面添加子元素,每个子元素会横向填充整个屏幕,然后能够实现子元素之间水平滑动,子元素内部也可以滑动,比如子元素是一个listview,这样可以实现一个自定义的ViewPager。效果图



2.预备知识点

什么是滑动冲突和怎么解决,滑动冲突就是在有多个元素的时候,事件不能正确的传递到要处理事件的view,导致不能正常滑动,比如一个可以横向滑动的view的里面有可以纵向滑动的list,这时候滑动就不知道该滑动哪一个等等。滑动冲突的解决方式主要是自己定制TouchEvent的分发机制,可以在父元素拦截事件确定分发的逻辑,也可以在子元素干扰父元素的事件分发来达到相同的效果,ScrollView采用的策略是在父元素里面写分发的逻辑

利用scroller进行滑动,滑动的逻辑实际上构成了一个可以退出的循环 onDraw->computeScroll->invalidate->onDraw 然后每次在computeScroll里面计算需要滑动到的位置,进行滑动,scroller里面就保存了整个滑动序列,这个序列会随时间变化,具体来说就是记录了滑动是否完成,和下一次需要滑到的地方。

事件分发的处理,这里需要从功能角度确定事件分发,具体的逻辑会在代码里面说明

布局的处理,onLayout里面进行子元素的布局,根据measure的尺寸进行子元素的布局

3.代码

3.1主代码

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
* Created by kisstheraik on 16/8/8.
* Description 可以左右滑动的view
*/
public class ScrollView extends ViewGroup {

private Scroller scroller;
private VelocityTracker velocityTracker;//用来测量速度
private int lastX=0;
private int lastY=0;
private int childIndex=0;
private int childWidth=0;
private int childSize=0;

public ScrollView(Context context){
super(context);
init();

}
public ScrollView(Context context,AttributeSet attributeSet){
super(context,attributeSet);
init();
}

public ScrollView(Context context,AttributeSet attributeSet,int def){
super(context,attributeSet,def);
init();
}

public void init(){

if(scroller==null) {
scroller = new Scroller(getContext());
velocityTracker=VelocityTracker.obtain();
}

}

//是否拦截某个事件,这个元素是父元素,使用外部拦截的方式来解决滑动冲突
@Override
public boolean onInterceptTouchEvent(MotionEvent ev){

boolean inter=false;

int x=(int)ev.getX();
int y=(int)ev.getY();

switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
inter=false;
//当还在滑动的时候就拦截按下的事件,然后退出滑动
if(!scroller.isFinished()){
scroller.abortAnimation();
inter=true;
}
break;
case MotionEvent.ACTION_MOVE:
int delX=x-lastX;
int delY=y-lastY;

if(Math.abs(delX)>Math.abs(delY)){//当x方向的滑动距离更大,说明还要继续滑动

inter=true;

}else inter=false;
break;
case MotionEvent.ACTION_UP://不拦截这个事件,但是子元素处理不了的时候会调用父元素的onTouchEvent方法
inter=false;
break;
default: inter=false;
break;
}

lastY=y;
lastX=x;
return inter;
}
//处理接收到的事件
@Override
public boolean onTouchEvent(MotionEvent ev){

velocityTracker.addMovement(ev);

int x=(int)ev.getX();
int y=(int)ev.getY();

switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
if(!scroller.isFinished()){
scroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int dex=x-lastX;
scrollBy(-dex,0);//这里是跟着手指在移动,因此需要瞬移
break;
case MotionEvent.ACTION_UP:
//需要在停下来的时候滑动到最近的child的边界上
int sx=getScrollX();
velocityTracker.computeCurrentVelocity(1000);
float xv=velocityTracker.getXVelocity();
if(Math.abs(xv)>=50){//速度达到一定的值就跳到下一个元素
childIndex=xv>0?childIndex-1:childIndex+1;
}else {
childIndex=(sx+childWidth/2)/childWidth;//否则看看哪一个元素占用的比例多
}

childIndex=Math.max(0,Math.min(childIndex,childSize-1));

int dx=childIndex*childWidth-sx;
smoothscroolBy(dx,0);//然后滑动到那里
velocityTracker.clear();
break;
default:break;
}

lastX=x;
lastY=y;
return true;

}

private void smoothscroolBy(int dx,int dy){

scroller.startScroll(getScrollX(),0,dx,0,500);
invalidate();

}

@Override
public void computeScroll(){
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}

//布局已经完成
@Override
protected void onLayout(boolean changed,int l,int t,int r,int b){

final int count=getChildCount();

int childLeft=0;

childSize=count;

childWidth=getChildAt(0).getWidth();
for(int i=0;i<count;++i){//完成每个子元素的布局
final View child=getChildAt(i);

if(child.getVisibility()!=GONE){
final int cwidth=child.getMeasuredWidth();

child.layout(childLeft,0,childLeft+cwidth,child.getMeasuredHeight());//调用子元素的layout方法完成布局,参数是子元素的开始位置

childLeft+=cwidth;
}

}

}
//测量本身和子元素,CircleView里面已经解释过了measure方法
@Override
protected void onMeasure(int wspe,int hspe){
super.onMeasure(wspe, hspe);

measureChildren(wspe,hspe);

int childNum=getChildCount();

int wmode=MeasureSpec.getMode(wspe);
int wsize=MeasureSpec.getSize(wspe);

int hmode=MeasureSpec.getMode(hspe);
int hsize=MeasureSpec.getSize(hspe);

if(childNum==0){
setMeasuredDimension(0,0);
}else if(wmode==MeasureSpec.AT_MOST&&hmode==MeasureSpec.AT_MOST){

final View child=getChildAt(0);
setMeasuredDimension(child.getMeasuredWidth()*childNum,child.getMeasuredHeight());

}else if(wmode==MeasureSpec.AT_MOST){
final View child=getChildAt(0);

setMeasuredDimension(child.getMeasuredWidth()*childNum,hsize);
}else if(hmode==MeasureSpec.AT_MOST){
final View child=getChildAt(0);

setMeasuredDimension(wsize,child.getMeasuredHeight());
}

}
//接下来是解决滑动冲突

}

3.2使用代码

布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>

<com.kejian.mike.scrollpage.ScrollView
android:layout_width="match_parent"
android:id="@+id/myscroll"
android:layout_height="wrap_content">

</com.kejian.mike.scrollpage.ScrollView>
</LinearLayout>

</RelativeLayout>

代码:

scrollView=(ScrollView)findViewById(R.id.myscroll);

LayoutInflater layoutInflater=getLayoutInflater();

WindowManager wm = (WindowManager) getApplication()
.getSystemService(Context.WINDOW_SERVICE);

for (int i = 0; i < 3; i++) {

ViewGroup layout = (ViewGroup) layoutInflater.inflate(
R.layout.scroll_view_content, scrollView, false);
layout.getLayoutParams().width = wm.getDefaultDisplay().getWidth();
TextView textView = (TextView) layout.findViewById(R.id.title);
textView.setText("page " + (i + 1));

createList(layout);
scrollView.addView(layout);
}

private void createList(ViewGroup layout) {
ListView listView = (ListView) layout.findViewById(R.id.list);
ArrayList<String> datas = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
}

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,datas);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this, "click item",
Toast.LENGTH_SHORT).show();

}
});
}

子元素的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:id="@+id/title"
android:layout_height="wrap_content" />
<ListView
android:layout_width="wrap_content"
android:id="@+id/list"
android:layout_height="wrap_content"></ListView>

</LinearLayout>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: