Android自定义控件(二)

这一篇主要来讲一下自定义控件中的自定义viewgroup,我们以项目中最常用的下拉刷新和加载更多组件为例

简单介绍一下自定义viewgroup时应该怎么做。

分析:下拉刷新和加载更多的原理和步骤

自定义一个viewgroup,将headerview、contentview和footerview从上到下依次布局,然后在初始化的时候

通过Scrooller滚动使得该组件在y轴方向上滚动headerview的高度,这样headerview就被隐藏了。而contentview的

宽度和高度都是match_parent的,因此屏幕上 headerview和footerview就都被隐藏在屏幕之外了。当contentview被

滚动到顶部,如果此时用户继续下拉,那么下拉刷新组件将拦截触摸事件,然后根据用户的触摸事件获取到手指滑动的

y轴距离,并通过scroller将该下拉组件在y轴上滚动手指滑动的距离,实现headerview的显示和隐藏,从而达到下拉的效果

。当用户滑动到最底部时会触发加载更多的操作,此时会通过scroller滚动该下拉刷新组件,将footerview显示出来,实现加载更多

的效果。具体步骤如下:

第一步:初始化View即headerView contentView和footerView
第二步:测量三个view的大小,并计算出viewgroup的大小
第三步:布局,将三个view在界面上布局,按照上中下的顺序
第四步:监听屏幕的触摸事件,判断是否下拉刷新或者加载更多
第五步:触发下拉刷新和加载更多事件执行下拉刷新和加载更多
第六步:下拉刷新和加载更多执行完后的重置操作

示例代码:

自定义的viewgroup

  1 package com.jiao.simpleimageview.view;
  2
  3 import android.content.Context;
  4 import android.graphics.Color;
  5 import android.support.v4.view.MotionEventCompat;
  6 import android.util.AttributeSet;
  7 import android.view.LayoutInflater;
  8 import android.view.MotionEvent;
  9 import android.view.View;
 10 import android.view.ViewGroup;
 11 import android.view.animation.RotateAnimation;
 12 import android.widget.AbsListView;
 13 import android.widget.AbsListView.OnScrollListener;
 14 import android.widget.ImageView;
 15 import android.widget.ProgressBar;
 16 import android.widget.Scroller;
 17 import android.widget.TextView;
 18
 19 import com.jiao.simpleimageview.R;
 20 import com.jiao.simpleimageview.listener.OnLoadListener;
 21 import com.jiao.simpleimageview.listener.OnRefreshListener;
 22
 23 import java.text.SimpleDateFormat;
 24 import java.util.Date;
 25
 26 /**
 27  * Created by jiaocg on 2016/3/24.
 28  */
 29 public abstract class RefreshLayoutBase<T extends View> extends ViewGroup implements
 30         OnScrollListener {
 31
 32     /**
 33      *
 34      */
 35     protected Scroller mScroller;
 36
 37     /**
 38      * 下拉刷新时显示的header view
 39      */
 40     protected View mHeaderView;
 41
 42     /**
 43      * 上拉加载更多时显示的footer view
 44      */
 45     protected View mFooterView;
 46
 47     /**
 48      * 本次触摸滑动y坐标上的偏移量
 49      */
 50     protected int mYOffset;
 51
 52     /**
 53      * 内容视图, 即用户触摸导致下拉刷新、上拉加载的主视图. 比如ListView, GridView等.
 54      */
 55     protected T mContentView;
 56
 57     /**
 58      * 最初的滚动位置.第一次布局时滚动header的高度的距离
 59      */
 60     protected int mInitScrollY = 0;
 61     /**
 62      * 最后一次触摸事件的y轴坐标
 63      */
 64     protected int mLastY = 0;
 65
 66     /**
 67      * 空闲状态
 68      */
 69     public static final int STATUS_IDLE = 0;
 70
 71     /**
 72      * 下拉或者上拉状态, 还没有到达可刷新的状态
 73      */
 74     public static final int STATUS_PULL_TO_REFRESH = 1;
 75
 76     /**
 77      * 下拉或者上拉状态
 78      */
 79     public static final int STATUS_RELEASE_TO_REFRESH = 2;
 80     /**
 81      * 刷新中
 82      */
 83     public static final int STATUS_REFRESHING = 3;
 84
 85     /**
 86      * LOADING中
 87      */
 88     public static final int STATUS_LOADING = 4;
 89
 90     /**
 91      * 当前状态
 92      */
 93     protected int mCurrentStatus = STATUS_IDLE;
 94
 95     /**
 96      * header中的箭头图标
 97      */
 98     private ImageView mArrowImageView;
 99     /**
100      * 箭头是否向上
101      */
102     private boolean isArrowUp;
103     /**
104      * header 中的文本标签
105      */
106     private TextView mTipsTextView;
107     /**
108      * header中的时间标签
109      */
110     private TextView mTimeTextView;
111     /**
112      * header中的进度条
113      */
114     private ProgressBar mProgressBar;
115     /**
116      * 屏幕高度
117      */
118     private int mScreenHeight;
119     /**
120      * Header 高度
121      */
122     private int mHeaderHeight;
123     /**
124      * 下拉刷新监听器
125      */
126     protected OnRefreshListener mOnRefreshListener;
127     /**
128      * 加载更多回调
129      */
130     protected OnLoadListener mLoadListener;
131
132     /**
133      * @param context
134      */
135     public RefreshLayoutBase(Context context) {
136         this(context, null);
137     }
138
139     /**
140      * @param context
141      * @param attrs
142      */
143     public RefreshLayoutBase(Context context, AttributeSet attrs) {
144         this(context, attrs, 0);
145     }
146
147     /**
148      * @param context
149      * @param attrs
150      * @param defStyle
151      */
152     public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyle) {
153         super(context, attrs);
154
155         // 初始化Scroller对象
156         mScroller = new Scroller(context);
157
158         // 获取屏幕高度
159         mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;
160         // header 的高度为屏幕高度的 1/4
161         mHeaderHeight = mScreenHeight / 4;
162
163         // 初始化整个布局
164         initLayout(context);
165     }
166
167     /**
168      * 第一步:初始化整个布局
169      *
170      * @param context
171      */
172     private final void initLayout(Context context) {
173         // header view
174         setupHeaderView(context);
175         // 设置内容视图
176         setupContentView(context);
177         // 设置布局参数
178         setDefaultContentLayoutParams();
179         // 添加mContentView
180         addView(mContentView);
181         // footer view
182         setupFooterView(context);
183
184     }
185
186     /**
187      * 初始化 header view
188      */
189     protected void setupHeaderView(Context context) {
190         mHeaderView = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this,
191                 false);
192         mHeaderView
193                 .setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
194                         mHeaderHeight));
195         mHeaderView.setBackgroundColor(Color.RED);
196         mHeaderView.setPadding(0, mHeaderHeight - 100, 0, 0);
197         addView(mHeaderView);
198
199         // HEADER VIEWS
200         mArrowImageView = (ImageView) mHeaderView.findViewById(R.id.pull_to_arrow_image);
201         mTipsTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);
202         mTimeTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_updated_at);
203         mProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);
204     }
205
206
207     /**
208      * 初始化Content View, 子类覆写.
209      */
210     protected abstract void setupContentView(Context context);
211
212     /**
213      * 设置Content View的默认布局参数
214      */
215     protected void setDefaultContentLayoutParams() {
216         ViewGroup.LayoutParams params =
217                 new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
218                         LayoutParams.MATCH_PARENT);
219         mContentView.setLayoutParams(params);
220     }
221
222     /**
223      * 初始化footer view
224      */
225     protected void setupFooterView(Context context) {
226         mFooterView = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_footer,
227                 this, false);
228         addView(mFooterView);
229     }
230
231
232     /**
233      * 第二步:测量
234      * 丈量视图的宽、高。宽度为用户设置的宽度,高度则为header,
235      * content view, footer这三个子控件的高度之和。
236      *
237      * @param widthMeasureSpec
238      * @param heightMeasureSpec
239      */
240     @Override
241     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
242         int width = MeasureSpec.getSize(widthMeasureSpec);
243         int childCount = getChildCount();
244         int finalHeight = 0;
245         for (int i = 0; i < childCount; i++) {
246             View child = getChildAt(i);
247             // measure
248             measureChild(child, widthMeasureSpec, heightMeasureSpec);
249             // 该view所需要的总高度
250             finalHeight += child.getMeasuredHeight();
251         }
252         setMeasuredDimension(width, finalHeight);
253     }
254
255
256     /**
257      * 第三步:布局
258      * 布局函数,将header, content view,
259      * footer这三个view从上到下布局。布局完成后通过Scroller滚动到header的底部,
260      * 即滚动距离为header的高度 +本视图的paddingTop,从而达到隐藏header的效果.
261      */
262     @Override
263     protected void onLayout(boolean changed, int l, int t, int r, int b) {
264
265         int childCount = getChildCount();
266         int top = getPaddingTop();
267         for (int i = 0; i < childCount; i++) {
268             View child = getChildAt(i);
269             child.layout(0, top, child.getMeasuredWidth(), child.getMeasuredHeight() + top);
270             top += child.getMeasuredHeight();
271         }
272
273         // 计算初始化滑动的y轴距离
274         mInitScrollY = mHeaderView.getMeasuredHeight() + getPaddingTop();
275         // 滑动到header view高度的位置, 从而达到隐藏header view的效果
276         scrollTo(0, mInitScrollY);
277     }
278
279
280     /**
281      * 第四步:监听滑动事件
282      * 与Scroller合作,实现平滑滚动。在该方法中调用Scroller的computeScrollOffset来判断滚动是否结束。
283      * 如果没有结束,
284      * 那么滚动到相应的位置,并且调用postInvalidate方法重绘界面,
285      * 从而再次进入到这个computeScroll流程,直到滚动结束。
286      */
287     @Override
288     public void computeScroll() {
289         if (mScroller.computeScrollOffset()) {
290             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
291             postInvalidate();
292         }
293     }
294
295     /*
296      * 在适当的时候拦截触摸事件,这里指的适当的时候是当mContentView滑动到顶部,
297      * 并且是下拉时拦截触摸事件,否则不拦截,交给其child
298      * view 来处理。
299      */
300     @Override
301     public boolean onInterceptTouchEvent(MotionEvent ev) {
302
303         final int action = MotionEventCompat.getActionMasked(ev);
304         // Always handle the case of the touch gesture being complete.
305         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
306             // Do not intercept touch event, let the child handle it
307             return false;
308         }
309
310         switch (action) {
311
312             case MotionEvent.ACTION_DOWN:
313                 mLastY = (int) ev.getRawY();
314                 break;
315
316             case MotionEvent.ACTION_MOVE:
317                 // int yDistance = (int) ev.getRawY() - mYDown;
318                 mYOffset = (int) ev.getRawY() - mLastY;
319                 // 如果拉到了顶部, 并且是下拉,则拦截触摸事件,从而转到onTouchEvent来处理下拉刷新事件
320                 if (isTop() && mYOffset > 0) {
321                     return true;
322                 }
323                 break;
324
325         }
326         // Do not intercept touch event, let the child handle it
327         return false;
328     }
329
330     /**
331      * 第五步:下拉刷新
332      * 1、滑动view显示出headerview
333      * 2、进度条滚动,修改标题内容
334      * 3、执行下拉刷新监听
335      * 4、刷新成功或失败后重置:隐藏headerview 修改标题内容
336      * 在这里处理触摸事件以达到下拉刷新或者上拉自动加载的问题
337      *
338      * @see android.view.View#onTouchEvent(android.view.MotionEvent)
339      */
340     @Override
341     public boolean onTouchEvent(MotionEvent event) {//下拉刷新的处理
342         switch (event.getAction()) {
343             case MotionEvent.ACTION_MOVE:
344                 int currentY = (int) event.getRawY();
345                 mYOffset = currentY - mLastY;
346                 if (mCurrentStatus != STATUS_LOADING) {
347                     changeScrollY(mYOffset);
348                 }
349
350                 rotateHeaderArrow();//旋转箭头
351                 changeTips();//重置文本
352                 mLastY = currentY;
353                 break;
354
355             case MotionEvent.ACTION_UP:
356                 // 下拉刷新的具体操作
357                 doRefresh();
358                 break;
359             default:
360                 break;
361         }
362         return true;
363     }
364
365     /**
366      * 设置滚动的参数
367      *
368      * @param yOffset
369      */
370     private void startScroll(int yOffset) {
371         mScroller.startScroll(getScrollX(), getScrollY(), 0, yOffset);
372         invalidate();
373     }
374
375     /**
376      * y轴上滑动到指定位置
377      *
378      * @param distance
379      * @return
380      */
381     protected void changeScrollY(int distance) {
382         // 最大值为 scrollY(header 隐藏), 最小值为0 ( header 完全显示).
383         int curY = getScrollY();
384         // 下拉
385         if (distance > 0 && curY - distance > getPaddingTop()) {
386             scrollBy(0, -distance);
387         } else if (distance < 0 && curY - distance <= mInitScrollY) {
388             // 上拉过程
389             scrollBy(0, -distance);
390         }
391
392         curY = getScrollY();
393         int slop = mInitScrollY / 2;
394         //
395         if (curY > 0 && curY < slop) {
396             mCurrentStatus = STATUS_RELEASE_TO_REFRESH;
397         } else if (curY > 0 && curY > slop) {
398             mCurrentStatus = STATUS_PULL_TO_REFRESH;
399         }
400     }
401
402
403     /**
404      * 旋转箭头图标
405      */
406     protected void rotateHeaderArrow() {
407
408         if (mCurrentStatus == STATUS_REFRESHING) {
409             return;
410         } else if (mCurrentStatus == STATUS_PULL_TO_REFRESH && !isArrowUp) {
411             return;
412         } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH && isArrowUp) {
413             return;
414         }
415
416         mProgressBar.setVisibility(View.GONE);
417         mArrowImageView.setVisibility(View.VISIBLE);
418         float pivotX = mArrowImageView.getWidth() / 2f;
419         float pivotY = mArrowImageView.getHeight() / 2f;
420         float fromDegrees = 0f;
421         float toDegrees = 0f;
422         if (mCurrentStatus == STATUS_PULL_TO_REFRESH) {
423             fromDegrees = 180f;
424             toDegrees = 360f;
425         } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) {
426             fromDegrees = 0f;
427             toDegrees = 180f;
428         }
429
430         RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
431         animation.setDuration(100);
432         animation.setFillAfter(true);
433         mArrowImageView.startAnimation(animation);
434
435         if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) {
436             isArrowUp = true;
437         } else {
438             isArrowUp = false;
439         }
440     }
441
442     /**
443      * 根据当前状态修改header view中的文本标签
444      */
445     protected void changeTips() {
446         if (mCurrentStatus == STATUS_PULL_TO_REFRESH) {
447             mTipsTextView.setText(R.string.pull_to_refresh_pull_label);
448         } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) {
449             mTipsTextView.setText(R.string.pull_to_refresh_release_label);
450         }
451     }
452
453
454     /**
455      * 手指抬起时,根据用户下拉的高度来判断是否是有效的下拉刷新操作。
456      * 如果下拉的距离超过header view的
457      * 1/2那么则认为是有效的下拉刷新操作,否则恢复原来的视图状态.
458      */
459     private void changeHeaderViewStaus() {
460         int curScrollY = getScrollY();
461         // 超过1/2则认为是有效的下拉刷新, 否则还原
462         if (curScrollY < mInitScrollY / 2) {
463             mScroller.startScroll(getScrollX(), curScrollY, 0, mHeaderView.getPaddingTop()
464                     - curScrollY);
465             mCurrentStatus = STATUS_REFRESHING;
466             mTipsTextView.setText(R.string.pull_to_refresh_refreshing_label);
467             mArrowImageView.clearAnimation();
468             mArrowImageView.setVisibility(View.GONE);
469             mProgressBar.setVisibility(View.VISIBLE);
470         } else {
471             mScroller.startScroll(getScrollX(), curScrollY, 0, mInitScrollY - curScrollY);
472             mCurrentStatus = STATUS_IDLE;
473         }
474
475         invalidate();
476     }
477
478     /**
479      * 执行下拉刷新
480      */
481     protected void doRefresh() {
482         changeHeaderViewStaus();
483         // 执行刷新操作
484         if (mCurrentStatus == STATUS_REFRESHING && mOnRefreshListener != null) {
485             mOnRefreshListener.onRefresh();
486         }
487     }
488
489     /**
490      * 刷新结束,恢复状态
491      */
492     public void refreshComplete() {
493         mScroller.startScroll(getScrollX(), getScrollY(), 0, mInitScrollY - getScrollY());
494         mCurrentStatus = STATUS_IDLE;
495         invalidate();
496         updateHeaderTimeStamp();
497
498         // 200毫秒后处理arrow和progressbar,免得太突兀
499         this.postDelayed(new Runnable() {
500
501             @Override
502             public void run() {
503                 mArrowImageView.setVisibility(View.VISIBLE);
504                 mProgressBar.setVisibility(View.GONE);
505             }
506         }, 100);
507
508     }
509
510     /**
511      * 修改header上的最近更新时间
512      */
513     private void updateHeaderTimeStamp() {
514         // 设置更新时间
515         mTimeTextView.setText(R.string.pull_to_refresh_update_time_label);
516         SimpleDateFormat sdf = (SimpleDateFormat) SimpleDateFormat.getInstance();
517         sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
518         mTimeTextView.append(sdf.format(new Date()));
519     }
520
521
522     /**
523      * 第六步:加载更多
524      * 滚动监听,当滚动到最底部,且用户设置了加载更多的监听器时触发加载更多操作.
525      * AbsListView, int, int, int)
526      */
527     @Override
528     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
529                          int totalItemCount) {
530         // 用户设置了加载更多监听器,且到了最底部,并且是上拉操作,那么执行加载更多.
531         if (mLoadListener != null && isBottom() && mScroller.getCurrY() <= mInitScrollY
532                 && mYOffset <= 0
533                 && mCurrentStatus == STATUS_IDLE) {
534             showFooterView();
535             doLoadMore();
536         }
537     }
538
539
540     @Override
541     public void onScrollStateChanged(AbsListView view, int scrollState) {
542
543     }
544
545     /**
546      * 执行下拉(自动)加载更多的操作
547      */
548     protected void doLoadMore() {
549         if (mLoadListener != null) {
550             mLoadListener.onLoadMore();
551         }
552     }
553     /**
554      * 显示footer view
555      */
556     private void showFooterView() {
557         startScroll(mFooterView.getMeasuredHeight());
558         mCurrentStatus = STATUS_LOADING;
559     }
560
561     /**
562      * 加载结束,恢复状态
563      */
564     public void loadCompelte() {
565         // 隐藏footer
566         startScroll(mInitScrollY - getScrollY());
567         mCurrentStatus = STATUS_IDLE;
568     }
569
570
571     /**
572      * 设置下拉刷新监听器
573      *
574      * @param listener
575      */
576     public void setOnRefreshListener(OnRefreshListener listener) {
577         mOnRefreshListener = listener;
578     }
579
580     /**
581      * 设置滑动到底部时自动加载更多的监听器
582      *
583      * @param listener
584      */
585     public void setOnLoadListener(OnLoadListener listener) {
586         mLoadListener = listener;
587     }
588
589
590     /**
591      * 是否已经到了最顶部,子类需覆写该方法,使得mContentView滑动到最顶端时返回true, 如果到达最顶端用户继续下拉则拦截事件;
592      *
593      * @return
594      */
595     protected abstract boolean isTop();
596
597     /**
598      * 是否已经到了最底部,子类需覆写该方法,使得mContentView滑动到最底端时返回true;从而触发自动加载更多的操作
599      *
600      * @return
601      */
602     protected abstract boolean isBottom();
603
604
605     /**
606      * 返回Content View
607      *
608      * @return
609      */
610     public T getContentView() {
611         return mContentView;
612     }
613
614     /**
615      * @return
616      */
617     public View getHeaderView() {
618         return mHeaderView;
619     }
620
621     /**
622      * @return
623      */
624     public View getFooterView() {
625         return mFooterView;
626     }
627
628 }

实现下拉刷新的listview

 1 package com.jiao.simpleimageview.view;
 2
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.widget.ListAdapter;
 6 import android.widget.ListView;
 7
 8 /**
 9  * Created by jiaocg on 2016/3/25.
10  */
11 public class RefreshListView extends RefreshLayoutBase<ListView> {
12     /**
13      * @param context
14      */
15     public RefreshListView(Context context) {
16         this(context, null);
17     }
18
19     /**
20      * @param context
21      * @param attrs
22      */
23     public RefreshListView(Context context, AttributeSet attrs) {
24         this(context, attrs, 0);
25     }
26
27     /**
28      * @param context
29      * @param attrs
30      * @param defStyle
31      */
32     public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
33         super(context, attrs, defStyle);
34     }
35
36     @Override
37     protected void setupContentView(Context context) {
38         mContentView = new ListView(context);
39         // 设置滚动监听器
40         mContentView.setOnScrollListener(this);
41
42     }
43
44     @Override
45     protected boolean isTop() {
46
47         //当第一个可见项是第一项时表示已经拉倒了顶部
48         return mContentView.getFirstVisiblePosition() == 0
49                 && getScrollY() <= mHeaderView.getMeasuredHeight();
50     }
51
52     @Override
53     protected boolean isBottom() {
54         //当最后一个可见项是最后一项时表示已经拉倒了底部
55         return mContentView != null && mContentView.getAdapter() != null
56                 && mContentView.getLastVisiblePosition() ==
57                 mContentView.getAdapter().getCount() - 1;
58     }
59
60     /**
61      * 设置adapter
62      */
63     public void setAdapter(ListAdapter adapter) {
64         mContentView.setAdapter(adapter);
65     }
66
67     public ListAdapter getAdapter() {
68         return mContentView.getAdapter();
69     }
70
71 }

然后直接在xml文件中引用使用即可实现,另外这种方式的下拉刷新扩展性很强

也可以实现TextView和GridView的刷新,只需继承该base实现其中的抽象方法即可

源码下载:https://yunpan.cn/cqKRSr2r2MsEk  提取密码:d177

时间: 2024-10-10 14:41:33

Android自定义控件(二)的相关文章

android 自定义控件二之仿QQ长按删除

自定义Dialog 1.先上个效果图: 虽然效果丑了点,但主要学习修改已有的控件,例如修改Dialog控件 2.一些基本的只是进行了解 Dialog: theme是Dialog的样式,常用样式为: <style name="MyDialogStyle" parent="@android:Theme.Dialog"> <item name="android:windowFrame">@null</item> &l

android自定义控件(二) 入门,继承View

转载请注明地址:http://blog.csdn.net/ethan_xue/article/details/7313788 ps: 可根据apidemo里LableView,list4,list6学习 文档在dev guide/Framework Topics/User Interface/Building Custom Components 自定义控件的步骤: 1 View的工作原理  2 编写View类  3 为View类增加属性  4 绘制屏幕  5 响应用户消息  6 自定义回调函数

玩转android自定义控件二——自定义索引栏listview

带索引栏的listview,在android开发非常普遍,方便用户进行字母索引,就像微信通讯录这样: 今天,我们就从零到一实现这个具有索引栏的listview. 怎么实现这个控件了,我们应当梳理出一个思路. ①首先应当将字母的索引栏继承与一个控件,通过ondraw方法将字母画出来. ②然后我们应该监听这个字母控件的ontouch事件,来判断用户到底是按了那个字母. ③就是实现这个索引栏与listview的联动,就是将listview滑动到按下字母的位置. 大体流程图如下: 有了前面铺垫,我们引出

老猪带你玩转android自定义控件二——自定义索引栏listview

带索引栏的listview,在android开发非常普遍,方便用户进行字母索引,就像微信通讯录这样: 今天,我们就从零到一实现这个具有索引栏的listview. 怎么实现这个控件了,我们应当梳理出一个思路. ①首先应当将字母的索引栏继承与一个控件,通过ondraw方法将字母画出来. ②然后我们应该监听这个字母控件的ontouch事件,来判断用户到底是按了那个字母. ③就是实现这个索引栏与listview的联动,就是将listview滑动到按下字母的位置. 大体流程图如下: 有了前面铺垫,我们引出

android 自定义控件 (二) 初步认识

最近一直在忙项目,也在不断的面试,每次问道这个自定义控件,好多人云里雾绕的,今天就这个机会,简单做个完全自定义控件的入门吧.上一篇讲了继承已有控件的过程,发现我们只是简答的在封装的布局里操作,并没有重写onDraw,onMeasure,onLayout这些方法.其实继承控件这种形式基本能满足我们大部分的功能,但对于现有控件无法满足的怎么办,那就让我们重写上述三个方法,自己写特定需求的控件.完全自定义控件一般有两种,一种继承View,一种继承Viewgroup.根据view树结构,我们知道View

Android 自定义控件开发入门(二)

上一次我们讲了一堆实现自定义控件的理论基础,列举了View类一些可以重写的方法,我们对这些方法的重写是我们继承View类来派生自定义控件的关键 我通过一个最简单的例子给大家展示了这一个过程,无论是多么复杂的自定义控件,思路总是这样子的,但是因为我们仅仅重写了onDraw方法使得大家觉得怪怪的,作为一个控件,我们居然还要为了他的实现为其增加麻烦的监听,这就不能叫做控件了. 下面再给大家介绍一个经常重写的方法法:publicboolean onTouchEvent (MotionEvent even

android自定义控件(二)Canvas

一.重要方法 1.translate2.scale3.rotate 二.注意 1.明确顺序 canvas.rotate(45); canvas.drawRect(new Rect(50, 50, 100, 100), paint); 如果顺序调换,则没有旋转的效果 2.转换的时候,需要把转换的中心点移到shape自身的中心 int left=50,top=50, right = 100,bottom = 100; canvas.translate(right/2, bottom/2); canv

Android自定义控件的实现步骤(二)

前天已经写了一个关于自定义控件的实现步骤的博客,这个是附上代码的详细版本 首先,我们得新建一个attrs.xml的资源文件,在上面添加我们将要自定义控件的额外属性,就是自定义控件的自定义属性,具体代码如下: <resources> <declare-styleable name="TestView"> <attr name="textColor" format="color"></attr> <

Android自定义控件View(二)

在前一篇博客中学习了Android自定义控件View的流程步骤和注意点,不了解的童鞋可以参考Android自定义控件View(一).这一节开始学习自定义控件View(二)之继承系统已有的控件.我们来自定义一个圆形ImageView. RoundImageView 随着Android UI效果越来越炫,很多系统自带的控件已经无法满足日常开发需求,比如很多应用的头像是圆形的,QQ头像就是圆形的图片.但是Android系统提供的控件当中没有一个是圆形的.那么怎么才能实现圆形头像效果呢?两种方法: 图片

Android自定义控件系列八:详解onMeasure()(二)--利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题

上一篇文章详细讲解了一下onMeasure/measure方法在Android自定义控件时的原理和作用,参看博文:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一),今天就来真正实践一下,让这两个方法大显神威来帮我们搞定图片的屏幕适配问题. 请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45038329,非允许请勿用于商业或盈利用途,违者必究. 使用ImageView会遇到