社会就是这样,你又不能改变社会,只能适应。
本篇文章作者会不断更新,小伙伴可以持续鼓励支持。不支持就停更,就是这么傲娇。github同步代码https://github.com/liulingfeng/CustomView
对于很多Android开发者而言,自定义view简直就是噩梦。想想都觉得好难,但自定义view又是高级进阶的必经之路。下面就我所知道的来讲一下自定义view吧,废话不多说,打卡上车。我将自定义view分为以下三类:
- 组合控件--代码复用
- 继承已有控件或ViewGroup--功能扩展
- 纯自定义,继承View
组合控件
并不需要我们自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。我们不生成控件,我们只是控件的搬运工。下面我就来创建一个公共的头部。
<pre>```<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"
android:background="@color/colorAccent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="This is Title"
android:textSize="18sp"
android:textColor="@android:color/white"
android:gravity="center"
android:layout_centerInParent="true"
/>
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="@mipmap/back"
android:layout_marginLeft="8dp"
/>
</RelativeLayout>
头部布局
</pre>
###### 在这里分享一个工具tools,在上面的布局中你可以发现它的身影。我给Textview设置的是 tools:text,而不是 android:text。这样设置的好处是这个文字只会在预览中看到,在正式运行到手机上是不会看到的。这个工具在ListView中特别好用,可以在ListView中设置item预览样式。
<pre>
```public class HeadView extends RelativeLayout implements View.OnClickListener{
private TextView tv_head;
private ImageView iv_back;
private Context mContext;
public HeadView(Context context) { this(context,null); }
public HeadView(Context context, AttributeSet attrs) {this(context, attrs,0);}
public HeadView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);
mContext = context;
//第一个参数:布局的资源id 第二个参数:填充的根视图 第三个参数是否将载入的视图绑定到根视图中
LayoutInflater.from(context).inflate(R.layout.layout_head,this,true);
tv_head = (TextView)findViewById(R.id.tv_head);
iv_back = (ImageView) findViewById(R.id.iv_back);
iv_back.setOnClickListener(this);
}
public void setTitle(String headTv){
tv_head.setText(headTv);
}
@Override
public void onClick(View view) {
if(view == iv_back){
((Activity)mContext).finish();
}
}
}```
自定义的头部
</pre>
*我这边简单的来说一下这里面三个构造方法的使用场景。第一种是在java代码中直接创建时使用;第二种是在xml定义时使用,但不引用style设置样式;第三种用脚趾头想都知道是在引用style设置样式时使用。我这边一股脑儿的把所有构造方法都放在第三种中处理,这是一种较为简便的写法。*
</p>
这个自定义组合控件的用法我就不详细说了,只需要像其他布局一样<类的完整地址></类的完整地址>
![大概结果.png](http://upload-images.jianshu.io/upload_images/1976633-7bf499842defb976.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
###### 好了姿势对了,接下来我再来奉献一个常用的组合控件--可删除的搜索框
<pre>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/searchbar_width"
android:layout_height="@dimen/searchbar_height"
android:layout_marginTop="16dp"
android:background="@drawable/searchbar_bg"
<ImageView
android:id="@+id/delete"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/close"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:visibility="invisible"
/>
<View
android:id="@+id/divider"
android:layout_width="1dp"
android:layout_height="24dp"
android:background="@color/colorAccent"
android:layout_toLeftOf="@id/delete"
android:layout_marginRight="6dp"
android:layout_centerVertical="true"
/>
<EditText
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/divider"
android:paddingLeft="12dp"
android:background="@null"
android:lines="1"
/>
</RelativeLayout>
搜索框布局
</pre>
###### 我这里的外边框用的是shape,而不是图片。至于为什么用shape,各位看官可以看我的另一票文章<http://www.jianshu.com/p/73317378d18c>。不知道如何写的同学可以咨询我。
<pre>
public class SearchBar extends LinearLayout implements View.OnClickListener,TextWatcher{
private ImageView delete;
private EditText input;
public SearchBar(Context context) {this(context,null);}
public SearchBar(Context context, AttributeSet attrs) {this(context, attrs,0);}
public SearchBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
View.inflate(context, R.layout.layout_searchbar,this);
delete = (ImageView)findViewById(R.id.delete);
input = (EditText)findViewById(R.id.input);
delete.setOnClickListener(this);
input.addTextChangedListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.delete:
input.setText("");
delete.setVisibility(View.INVISIBLE);
break;
}
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
@Override
public void afterTextChanged(Editable editable) {
if(editable.length()!=0){
delete.setVisibility(View.VISIBLE);
}
else{
delete.setVisibility(View.INVISIBLE);
}
}
}
自定义的搜索框
</pre>
###### 之前没有写明使用的正确姿势,昨天晚上我一直辗转反侧睡不着。在这里我想应该写一下使用的正确姿势。
<pre>
<com.llf.loding.view.SearchBar xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal" >
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:layout_marginTop="16dp"
></View>
</com.llf.loding.view.SearchBar>
使用的正确姿势,我这样使用可以减少一层布局
</pre>
###### 最后上结果图
![input.gif](http://upload-images.jianshu.io/upload_images/1976633-9c669445d7a52ddc.gif?imageMogr2/auto-orient/strip)
> 不要带有目的性的去做一件事情,这样往往达不到目的。
###### 好的,咱们换个高深点的姿势,来自定义一个头部广告。这边有两种思路,一种是用ViewPager;一种是用ViewFlipper。我这边是用ViewFlipper实现。
<pre>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/iv_height"
android:orientation="vertical">
<com.llf.loding.view.NotifiableViewFlipper
android:id="@+id/viewflipper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoStart="true"
android:inAnimation="@anim/slide_in"
android:outAnimation="@anim/slide_out"
android:flipInterval="2000"
android:persistentDrawingCache="animation"
/>
<LinearLayout
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:gravity="center"
android:orientation="horizontal"
android:background="#15000000"
/>
</RelativeLayout>
广告位布局
</pre>
###### 细心的同学可能已经发现我在这里不小心用了另外一招--继承已有控件。大家不要打我,我这里也是逼不得已,这个ViewFlipper很奇葩,没有滑动到哪个view的回调接口,大家看下去就会明白我不是故意的。这里先上这个自定义的ViewFlipper。
<pre>
public class NotifiableViewFlipper extends ViewFlipper {
private OnFlipListener onFlipListener;
public static interface OnFlipListener {
public void onShowPrevious(NotifiableViewFlipper flipper);
public void onShowNext(NotifiableViewFlipper flipper);
}
public void setOnFlipListener(
OnFlipListener onFlipListener) {
this.onFlipListener = onFlipListener;
}
public NotifiableViewFlipper(Context context) { super(context); }
public NotifiableViewFlipper(Context context, AttributeSet attrs) {super(context, attrs); }
@Override
public void showPrevious() {
super.showPrevious();
if (hasFlipListener()) {
onFlipListener.onShowPrevious(this);
}
}
@Override
public void showNext() {
super.showNext();
if (hasFlipListener()) {
onFlipListener.onShowNext(this);
}
}
}
</pre>
###### 该是上主角的时候了,下面是自定义的广告位。
<pre>
public class AdvertisementLayout extends LinearLayout implements NotifiableViewFlipper.OnFlipListener{
private Context mContext;
private NotifiableViewFlipper viewFlipper;
private LinearLayout indicator;
private List<Integer> datas;
private ImageView[] images;
private int currentItem;
public AdvertisementLayout(Context context) { this(context,null);}
public AdvertisementLayout(Context context, AttributeSet attrs) { this(context, attrs,0); }
public AdvertisementLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init(){
View.inflate(mContext, R.layout.layout_advertisement,this);
viewFlipper = (NotifiableViewFlipper)findViewById(R.id.viewflipper);
indicator = (LinearLayout)findViewById(R.id.indicator);
viewFlipper.setOnFlipListener(this);
}
public void setData(List<Integer> data){
this.datas = data;
instantiation();
}
private void instantiation(){
images = new ImageView[datas.size()];
for (int i=0;i<datas.size();i++){
ImageView imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,(int)mContext.getResources().getDimension(R.dimen.iv_height)));
imageView.setImageResource(datas.get(i));
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext,viewFlipper.getDisplayedChild()+"被点击了",Toast.LENGTH_SHORT).show();
}
});
viewFlipper.addView(imageView);
}
for (int i=0;i<datas.size();i++){
ImageView imageView = new ImageView(mContext);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(30,30);
layoutParams.setMargins(10,2,10,2);
imageView.setLayoutParams(layoutParams);
imageView.setBackgroundResource(R.drawable.indicator_iv);
imageView.setEnabled(false);
indicator.addView(imageView);
images[i] = imageView;
}
images[0].setEnabled(true);
}
@Override
public void onShowPrevious(NotifiableViewFlipper flipper) {
images[currentItem].setEnabled(false);
images[flipper.getDisplayedChild()].setEnabled(true);
currentItem = flipper.getDisplayedChild();
}
@Override
public void onShowNext(NotifiableViewFlipper flipper) {
images[currentItem].setEnabled(false);
images[flipper.getDisplayedChild()].setEnabled(true);
currentItem = flipper.getDisplayedChild();
}
</pre>
###### 上图咯
![advertise.gif](http://upload-images.jianshu.io/upload_images/1976633-1d4c9355ee0ee396.gif?imageMogr2/auto-orient/strip)
> 我们愤怒,不是因为不公平,而是自己处在不公平的位置上。
###### 接下来我们开始讲继承已有View或ViewGroup,说白了就是扩展功能。在讲这个之前我们先来看两个知识点:
###### 1. 涉及到的常用方法
- onFinishInflate()-当应用从XML加载该组件并用它构建界面之后调用的方法。
- onMeasure()-检测View组件及其子组件的大小,此方法执行之后才能获取到控件的大小。
- onLayout()-当该组件需要分配其子组件的位置、大小时。一般在viewGroup中才会使用到此方法。
- onSizeChange()-当该组件的大小被改变时调用。
- onDraw() -当组件将要绘制它的内容时调用。
- onTouchEvent()-当发生触屏事件时调用。
- onWindowFocusChanged(boolean) 当该组件得到、失去焦点时调用。
- onAtrrachedToWindow() 当把该组件放入到某个窗口时调用。
- onDetachedFromWindow() 当把该组件从某个窗口上分离时触发的方法。
- onWindowVisibilityChanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法。
###### 我这里详细讲一下onMeasure方法和onLayout方法。说得通俗一点onMeasure方法就是告诉父容器你要多大的地方。对于ViewGroup在这里需要递归的去计算每一个子视图的大小。onMeasure方法有两个参数(widthMeasureSpec,heightMeasureSpec)表示布局期望的子控件规格。等一下我放一张图就好理解了。在这里还需要知道三种测量模式:
1.EXACTLY 表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
2.AT_MOST 表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
3.UNSPECIFIED 表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。
具体可参考<http://blog.csdn.net/dmk877/article/details/49558367>
<pre>
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int resultWidth = 100;//设置一个默认宽度
if(modeWidth == MeasureSpec.EXACTLY){
resultWidth = sizeWidth;
}else{
resultWidth = Math.min(resultWidth,sizeWidth);
}
int resultHeight = 100;//设置一个默认高度
if(modeHeight == MeasureSpec.EXACTLY){
resultHeight = sizeHeight; }
else{
resultHeight = Math.min(resultHeight,sizeHeight);
}
//此句必须要写
setMeasuredDimension(resultWidth,resultHeight);}
简单的重写onMeasure的列子</pre>
###### 接下来我就要用重写onMeasure方法来打造能适配图片的ImageView。这里有一篇个人感觉不错的适配文章<http://www.zcool.com.cn/article/ZNjI3NDQ=.html>
<pre>
public class CleverImageView extends ImageView {
private double scale = 3.6;//宽高比
public CleverImageView(Context context) { this(context, null); }
public CleverImageView(Context context, AttributeSet attrs) {this(context, attrs, 0); }
public CleverImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
if (modeWidth == MeasureSpec.EXACTLY && modeHeight == MeasureSpec.AT_MOST) {
sizeHeight = (int) (sizeWidth / scale + 0.5f);//这里加0.5f是为了四舍五入
}
else if (modeHeight == MeasureSpec.EXACTLY && modeWidth == MeasureSpec.AT_MOST) {
sizeWidth = (int) (sizeHeight / scale + 0.5f); }
setMeasuredDimension(sizeWidth,sizeHeight);
}
public void setScale(double scale) {
this.scale = scale;
}
}
其实ImageView的adjustbound属性就能解决这个问题,我这边只是拿这个举个栗子。
</pre>
###### 知道了大小之后,通过onLayout就是解决放在哪里和怎么放的问题。各位可以看一下流式布局<https://github.com/blazsolar/FlowLayout>,对这两个方法诠释的比较清楚。我这边简单的实现了一个。
<pre>
public class FlowLayoutSp extends ViewGroup {
public FlowLayoutSp(Context context) { super(context); }
public FlowLayoutSp(Context context, AttributeSet attrs) {super(context, attrs);}
public FlowLayoutSp(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
/** * 只有在内容为wrap情况下才需要记录width和height * width:总宽度 * height:总高度 * lineWidth:行宽 * lineHeight :行高 */
int width = 0;
int height = 0;
int lineWidth = 0;
int lineHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
lineHeight = Math.max(lineHeight, childHeight);
lineWidth += childWidth;
if (lineWidth > sizeWidth) {
lineWidth = childWidth;
height += lineHeight;
}else{
width = Math.max(width, lineWidth);
}
if (i == getChildCount() - 1) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height); }
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
int lineWidth = 0;
int lineHeight = 0;
int width = getWidth();//ViewGroup的总宽
int height = 0;//目前的行高
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int chidWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
left = lp.leftMargin + lineWidth;
right = lp.rightMargin + chidWidth + lp.leftMargin + lineWidth;
top = lp.topMargin + height;
bottom = lp.bottomMargin + childHeight + lp.topMargin + height;
lineWidth = right;
if(lineWidth > width) {
lineWidth = 0;
left = lp.leftMargin + lineWidth;
right = lp.rightMargin + chidWidth + lp.leftMargin + lineWidth;
height += lineHeight;
top = lp.topMargin + height;
bottom = lp.bottomMargin + childHeight + lp.topMargin + height;
lineWidth = right;
}
child.layout(left, top, right-lp.rightMargin, bottom-lp.bottomMargin);
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
</pre>
![递归.png](http://upload-images.jianshu.io/upload_images/1976633-9d9ec7706dc3075f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> 每一个ViewGroup负责要求它的每一个孩子被绘制,每一个View负责绘制自己。
###### 2.自定义属性,即在xml设置属性或则设置style。流程如下:
- 在res/values文件下定义一个attrs.xml文件。建立declare-styleable,取一个自己想取得名字,不过最好是跟自定义控件的名字保持一致。
- 定义需要用到的各个属性,属性的类型在下方表格中给出。属性定义时可以指定多种类型值。
- 在自定义控件中获取定义的值。一定要注意最后需要回收。
![属性取值类型.png](http://upload-images.jianshu.io/upload_images/1976633-18dfbd4d803f20b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
###### 废话不多说,上列子了。这个是一个通过继承HorizontalScrollView实现类似qq侧滑效果。注意这边的属性动画用的nineoldandroids框架的.
<pre>
public class SlideMenu extends HorizontalScrollView{
private int screenWidth;
private DisplayMetrics outMetrics;
private int margenRight, menuWidth;
private LinearLayout mWapper;
private ViewGroup mMenu;
private ViewGroup mContent;
private boolean first = true;
public SlideMenu(Context context, AttributeSet attrs) { super(context, attrs);
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
screenWidth = outMetrics.widthPixels;
margenRight = 100;
}
/* * 设置子view的宽和高 设置viewGroup的宽和高。目的是设置左边布局的一个偏移量 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (first) {
mWapper = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) mWapper.getChildAt(0);
mContent = (ViewGroup) mWapper.getChildAt(1);
menuWidth = mMenu.getLayoutParams().width = screenWidth-margenRight;
mContent.getLayoutParams().width = screenWidth;
first = false;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//一开始向左滚动screenWidth - margenRight
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
this.scrollTo(screenWidth - margenRight, 0);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();
//当滑动的距离大于menu宽度的一半自动滚动menuWidth,否则滚回起始点
if (scrollX >= menuWidth / 2) {
//smoothScrollTo方法滚动平缓
this.smoothScrollTo(menuWidth, 0);
} else {
this.smoothScrollTo(0, 0);
}
return true;
}
return super.onTouchEvent(ev);
}
/** * 滚动发生时 */
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
//由于刚开始时向左边滚动了menuWidth,所以l的起始值为menuWidth
float scale = l x 1.0f / menuWidth; // 1 ~ 0
float rightScale = 0.7f + 0.3f x scale;
float leftScale = 1.0f - scale x 0.3f;
float leftAlpha = 0.6f + 0.4f x (1 - scale);
// 调用属性动画
ViewHelper.setTranslationX(mMenu, menuWidth * scale* 0.8f);
ViewHelper.setPivotX(mContent, 0);
ViewHelper.setPivotY(mContent, getHeight() / 2);
ViewHelper.setScaleX(mContent, rightScale);
ViewHelper.setScaleY(mContent, rightScale);
ViewHelper.setScaleX(mMenu, leftScale);
ViewHelper.setScaleY(mMenu, leftScale);
ViewHelper.setAlpha(mMenu, leftAlpha);
}
}
</pre>
![侧滑.gif](http://upload-images.jianshu.io/upload_images/1976633-bcd6288ebe7ffa85.gif?imageMogr2/auto-orient/strip)
#####细心的朋友可能已经注意到上方的列子中第一个子View右边的偏移量margenRight 我是写死的。这时候吐糟声就来了“你这个很不优雅啊,我想自定义怎么办啊,我想在xml中设置怎么办啊”。客官别急,这就满足你的要求。且看下方修改后的代码。
- 首先建立declare-styleable,说白了就是定义你需要用到的属性。在这之前先在values下建立一个叫attr的文件,所有declare-styleable放在它里面
<resources>
<declare-styleable name="SlideMenu">
<attr name="marginRight" format="dimension"></attr>
</declare-styleable>
</resources>
- 接下来就是修改SlideMenu ,我这边单放修改了的代码,因为我懒
public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.SlideMenu);
margenRight = array.getInteger(R.styleable.SlideMenu_marginRight,(int)getResources().getDimension(R.dimen.marginRight));//我这边设置了默认偏移量是50dp
array.recycle();//最后别忘了回收TypedArray
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
screenWidth = outMetrics.widthPixels;}
- 然后你就可以为所欲为了,我们来看下xml中怎么用
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
//这边添加命名空间,名字你可以随意取,我这边取得是app。我这简单的一种写法。还有一种写法是后面跟自定义view所在的包名
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<com.llf.wheelview.SlideMenu
android:layout_width="match_parent"
android:layout_height="match_parent"
app:marginRight="@dimen/marginRight"
>
</com.llf.wheelview.SlideMenu>
</LinearLayout>
##纯自定义
在讲这个之前先要学习一下动画和绘图
动画可以参考我在github上托管的项目,比较详细<https://github.com/liulingfeng/Animation>
绘画看我的另外一篇文章<http://www.jianshu.com/p/d92f1bebd3ab>
######学好了基本功开始学习心法