让Android Support V4中的SwipeRefreshLayout支持上拉加载更多

前言 原 来的Android SDK中并没有下拉刷新组件,但是这个组件确实绝大多数APP必备的一个部件。好在google在v4包中出了一个 SwipeRefreshLayout,但是这个组件只支持下拉刷新,不支持上拉加载更多的操作。因此,我们就来简单的扩展一下这个组件以实现上拉下载的 目的。 基本原理 上 拉加载或者说滚动到底部时自动加载,都是通过判断是否滚动到了ListView或者其他View的底部,然后触发相应的操作,这里我们以ListView 来说明。因此我们需要在监听ListView的滚动事件,当ListView滚动到底部时自动触发加载操作;但是当用户支持手指滑动屏幕,没有滚动时,我 们也需要让它加载,因此这种情形就是上拉加载更多。所以我们需要在触摸事件里面进行判断,如果到了底部,且用户是上拉操作,那么执行加载更多。更多关于下 拉刷新、上拉加载请参考[打造通用的Android下拉刷新组件(适用于ListView、GridView等各类View)](http://blog.csdn.net/bboyfeiyu/article/details/39718861)。 时间有限,直接上代码吧。 实现代码 **[java]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> </div> - <span class="comment">/**</span> - <span class="comment"> * 继承自SwipeRefreshLayout,从而实现滑动到底部时上拉加载更多的功能.</span> - <span class="comment"> * </span> - <span class="comment"> * @author mrsimple</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> RefreshLayout <span class="keyword">extends</span> SwipeRefreshLayout <span class="keyword">implements</span> OnScrollListener { - - <span class="comment">/**</span> - <span class="comment"> * 滑动到最下面时的上拉操作</span> - <span class="comment"> */</span> - - <span class="keyword">private</span> <span class="keyword">int</span> mTouchSlop; - <span class="comment">/**</span> - <span class="comment"> * listview实例</span> - <span class="comment"> */</span> - <span class="keyword">private</span> ListView mListView; - - <span class="comment">/**</span> - <span class="comment"> * 上拉监听器, 到了最底部的上拉加载操作</span> - <span class="comment"> */</span> - <span class="keyword">private</span> OnLoadListener mOnLoadListener; - - <span class="comment">/**</span> - <span class="comment"> * ListView的加载中footer</span> - <span class="comment"> */</span> - <span class="keyword">private</span> View mListViewFooter; - - <span class="comment">/**</span> - <span class="comment"> * 按下时的y坐标</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mYDown; - <span class="comment">/**</span> - <span class="comment"> * 抬起时的y坐标, 与mYDown一起用于滑动到底部时判断是上拉还是下拉</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mLastY; - <span class="comment">/**</span> - <span class="comment"> * 是否在加载中 ( 上拉加载更多 )</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> isLoading = <span class="keyword">false</span>; - - <span class="comment">/**</span> - <span class="comment"> * @param context</span> - <span class="comment"> */</span> - <span class="keyword">public</span> RefreshLayout(Context context) { - <span class="keyword">this</span>(context, <span class="keyword">null</span>); - } - - <span class="keyword">public</span> RefreshLayout(Context context, AttributeSet attrs) { - <span class="keyword">super</span>(context, attrs); - - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - - mListViewFooter = LayoutInflater.from(context).inflate(R.layout.listview_footer, <span class="keyword">null</span>, - <span class="keyword">false</span>); - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onLayout(<span class="keyword">boolean</span> changed, <span class="keyword">int</span> left, <span class="keyword">int</span> top, <span class="keyword">int</span> right, <span class="keyword">int</span> bottom) { - <span class="keyword">super</span>.onLayout(changed, left, top, right, bottom); - - <span class="comment">// 初始化ListView对象</span> - <span class="keyword">if</span> (mListView == <span class="keyword">null</span>) { - getListView(); - } - } - - <span class="comment">/**</span> - <span class="comment"> * 获取ListView对象</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> getListView() { - <span class="keyword">int</span> childs = getChildCount(); - <span class="keyword">if</span> (childs > <span class="number"></span>) { - View childView = getChildAt(<span class="number"></span>); - <span class="keyword">if</span> (childView <span class="keyword">instanceof</span> ListView) { - mListView = (ListView) childView; - <span class="comment">// 设置滚动监听器给ListView, 使得滚动的情况下也可以自动加载</span> - mListView.setOnScrollListener(<span class="keyword">this</span>); - Log.d(VIEW_LOG_TAG, <span class="string">&#8220;### 找到listview&#8221;</span>); - } - } - } - - <span class="comment">/*</span> - <span class="comment"> * (non-Javadoc)</span> - <span class="comment"> * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent)</span> - <span class="comment"> */</span> - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> dispatchTouchEvent(MotionEvent event) { - <span class="keyword">final</span> <span class="keyword">int</span> action = event.getAction(); - - <span class="keyword">switch</span> (action) { - <span class="keyword">case</span> MotionEvent.ACTION_DOWN: - <span class="comment">// 按下</span> - mYDown = (<span class="keyword">int</span>) event.getRawY(); - <span class="keyword">break</span>; - - <span class="keyword">case</span> MotionEvent.ACTION_MOVE: - <span class="comment">// 移动</span> - mLastY = (<span class="keyword">int</span>) event.getRawY(); - <span class="keyword">break</span>; - - <span class="keyword">case</span> MotionEvent.ACTION_UP: - <span class="comment">// 抬起</span> - <span class="keyword">if</span> (canLoad()) { - loadData(); - } - <span class="keyword">break</span>; - <span class="keyword">default</span>: - <span class="keyword">break</span>; - } - - <span class="keyword">return</span> <span class="keyword">super</span>.dispatchTouchEvent(event); - } - - <span class="comment">/**</span> - <span class="comment"> * 是否可以加载更多, 条件是到了最底部, listview不在加载中, 且为上拉操作.</span> - <span class="comment"> * </span> - <span class="comment"> * @return</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> canLoad() { - <span class="keyword">return</span> isBottom() && !isLoading && isPullUp(); - } - - <span class="comment">/**</span> - <span class="comment"> * 判断是否到了最底部</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> isBottom() { - - <span class="keyword">if</span> (mListView != <span class="keyword">null</span> && mListView.getAdapter() != <span class="keyword">null</span>) { - <span class="keyword">return</span> mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() &#8211; <span class="number">1</span>); - } - <span class="keyword">return</span> <span class="keyword">false</span>; - } - - <span class="comment">/**</span> - <span class="comment"> * 是否是上拉操作</span> - <span class="comment"> * </span> - <span class="comment"> * @return</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> isPullUp() { - <span class="keyword">return</span> (mYDown &#8211; mLastY) >= mTouchSlop; - } - - <span class="comment">/**</span> - <span class="comment"> * 如果到了最底部,而且是上拉操作.那么执行onLoad方法</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> loadData() { - <span class="keyword">if</span> (mOnLoadListener != <span class="keyword">null</span>) { - <span class="comment">// 设置状态</span> - setLoading(<span class="keyword">true</span>); - <span class="comment">//</span> - mOnLoadListener.onLoad(); - } - } - - <span class="comment">/**</span> - <span class="comment"> * @param loading</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">void</span> setLoading(<span class="keyword">boolean</span> loading) { - isLoading = loading; - <span class="keyword">if</span> (isLoading) { - mListView.addFooterView(mListViewFooter); - } <span class="keyword">else</span> { - mListView.removeFooterView(mListViewFooter); - mYDown = <span class="number"></span>; - mLastY = <span class="number"></span>; - } - } - - <span class="comment">/**</span> - <span class="comment"> * @param loadListener</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">void</span> setOnLoadListener(OnLoadListener loadListener) { - mOnLoadListener = loadListener; - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onScrollStateChanged(AbsListView view, <span class="keyword">int</span> scrollState) { - - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onScroll(AbsListView view, <span class="keyword">int</span> firstVisibleItem, <span class="keyword">int</span> visibleItemCount, - <span class="keyword">int</span> totalItemCount) { - <span class="comment">// 滚动时到了最底部也可以加载更多</span> - <span class="keyword">if</span> (canLoad()) { - loadData(); - } - } - - <span class="comment">/**</span> - <span class="comment"> * 加载更多的监听器</span> - <span class="comment"> * </span> - <span class="comment"> * @author mrsimple</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">interface</span> OnLoadListener { - <span class="keyword">public</span> <span class="keyword">void</span> onLoad(); - } - } listview_footer.xml: **[html]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2"> </embed> </div> </div> </div> - <span class="tag"><?</span><span class="tag-name">xml</span> <span class="attribute">version</span>=<span class="attribute-value">&#8220;1.0&#8221;</span> <span class="attribute">encoding</span>=<span class="attribute-value">&#8220;utf-8&#8221;</span><span class="tag">?></span> - <span class="tag"><</span><span class="tag-name">RelativeLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;fill_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:background</span>=<span class="attribute-value">&#8220;@color/umeng_comm_comments_bg&#8221;</span> - <span class="attribute">android:gravity</span>=<span class="attribute-value">&#8220;center&#8221;</span> - <span class="attribute">android:paddingBottom</span>=<span class="attribute-value">&#8220;8dip&#8221;</span> - <span class="attribute">android:paddingTop</span>=<span class="attribute-value">&#8220;5dip&#8221;</span> <span class="tag">></span> - - <span class="tag"><</span><span class="tag-name">ProgressBar</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/pull_to_refresh_load_progress&#8221;</span> - <span class="attribute">style</span>=<span class="attribute-value">&#8220;@android:style/Widget.ProgressBar.Small.Inverse&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_centerVertical</span>=<span class="attribute-value">&#8220;true&#8221;</span> - <span class="attribute">android:layout_centerHorizontal</span>=<span class="attribute-value">&#8220;true&#8221;</span> - <span class="attribute">android:paddingRight</span>=<span class="attribute-value">&#8220;100dp&#8221;</span> - <span class="attribute">android:indeterminate</span>=<span class="attribute-value">&#8220;true&#8221;</span> <span class="tag">/></span> - - <span class="tag"><</span><span class="tag-name">TextView</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/pull_to_refresh_loadmore_text&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;fill_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_gravity</span>=<span class="attribute-value">&#8220;center&#8221;</span> - <span class="attribute">android:gravity</span>=<span class="attribute-value">&#8220;center&#8221;</span> - <span class="attribute">android:paddingTop</span>=<span class="attribute-value">&#8220;5dip&#8221;</span> - <span class="attribute">android:text</span>=<span class="attribute-value">&#8220;@string/load&#8221;</span> - <span class="attribute">android:textAppearance</span>=<span class="attribute-value">&#8220;?android:attr/textAppearanceMedium&#8221;</span> - <span class="attribute">android:textColor</span>=<span class="attribute-value">&#8220;@android:color/darker_gray&#8221;</span> - <span class="attribute">android:textSize</span>=<span class="attribute-value">&#8220;14sp&#8221;</span> - <span class="attribute">android:textStyle</span>=<span class="attribute-value">&#8220;bold&#8221;</span> <span class="tag">/></span> - - <span class="tag"></</span><span class="tag-name">RelativeLayout</span><span class="tag">></span> 使用示例 refresh.xml布局文件: **[html]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3"> </embed> </div> </div> </div> - <span class="tag"><?</span><span class="tag-name">xml</span> <span class="attribute">version</span>=<span class="attribute-value">&#8220;1.0&#8221;</span> <span class="attribute">encoding</span>=<span class="attribute-value">&#8220;utf-8&#8221;</span><span class="tag">?></span> - <span class="tag"><</span><span class="tag-name">myview.RefreshLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/swipe_layout&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> <span class="tag">></span> - - <span class="tag"><</span><span class="tag-name">ListView</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/listview&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> <span class="tag">></span> - <span class="tag"></</span><span class="tag-name">ListView</span><span class="tag">></span> - - <span class="tag"></</span><span class="tag-name">myview.RefreshLayout</span><span class="tag">></span> activity中的使用 : **[java]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4"> </embed> </div> </div> </div> - <span class="comment">/**</span> - <span class="comment"> * @author mrsimple</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity { - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState) { - <span class="keyword">super</span>.onCreate(savedInstanceState); - - setContentView(R.layout.refresh); - - <span class="comment">// 模拟一些数据</span> - <span class="keyword">final</span> List<String> datas = <span class="keyword">new</span> ArrayList<String>(); - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < <span class="number">20</span>; i++) { - datas.add(<span class="string">&#8220;item &#8211; &#8220;</span> + i); - } - - <span class="comment">// 构造适配器</span> - <span class="keyword">final</span> BaseAdapter adapter = <span class="keyword">new</span> ArrayAdapter<String>(<span class="keyword">this</span>, - android.R.layout.simple_list_item_1, - datas); - <span class="comment">// 获取listview实例</span> - ListView listView = (ListView) findViewById(R.id.listview); - listView.setAdapter(adapter); - - <span class="comment">// 获取RefreshLayout实例</span> - <span class="keyword">final</span> RefreshLayout myRefreshListView = (RefreshLayout) - findViewById(R.id.swipe_layout); - - <span class="comment">// 设置下拉刷新时的颜色值,颜色值需要定义在xml中</span> - myRefreshListView - .setColorScheme(R.color.umeng_comm_text_topic_light_color, - R.color.umeng_comm_yellow, R.color.umeng_comm_green, - R.color.umeng_comm_linked_text); - <span class="comment">// 设置下拉刷新监听器</span> - myRefreshListView.setOnRefreshListener(<span class="keyword">new</span> OnRefreshListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onRefresh() { - - Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&#8220;refresh&#8221;</span>, Toast.LENGTH_SHORT).show(); - - myRefreshListView.postDelayed(<span class="keyword">new</span> Runnable() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() { - <span class="comment">// 更新数据</span> - datas.add(<span class="keyword">new</span> Date().toGMTString()); - adapter.notifyDataSetChanged(); - <span class="comment">// 更新完后调用该方法结束刷新</span> - myRefreshListView.setRefreshing(<span class="keyword">false</span>); - } - }, <span class="number">1000</span>); - } - }); - - <span class="comment">// 加载监听器</span> - myRefreshListView.setOnLoadListener(<span class="keyword">new</span> OnLoadListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onLoad() { - - Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&#8220;load&#8221;</span>, Toast.LENGTH_SHORT).show(); - - myRefreshListView.postDelayed(<span class="keyword">new</span> Runnable() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() { - datas.add(<span class="keyword">new</span> Date().toGMTString()); - adapter.notifyDataSetChanged(); - <span class="comment">// 加载完后调用该方法</span> - myRefreshListView.setLoading(<span class="keyword">false</span>); - } - }, <span class="number">1500</span>); - - } - }); - } - - } 效果图 ![](http://img.blog.csdn.net/20141009181845718) github链接 : [下拉刷新库](https://github.com/bboyfeiyu/android_my_pull_refresh_view) 。 示例在android_my_pull_demo工程中,进入demo后点击最后一个按钮查看效果即可。注意,在刷新和加载时,需要有一定的延时才能看到效果,这里我们用postDelay来模拟网络请求等延时操作,否则将看不到加载效果。

2015年6月12日 · 7 分钟 · 天边的星星

Android下拉刷新上拉加载控件,对所有View通用!

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38868463 前面写过一篇关于下拉刷新控件的博客下拉刷新控件终结者:PullToRefreshLayout,后来看到好多人还有上拉加载更多的需求,于是就在前面下拉刷新控件的基础上进行了改进,加了上拉加载的功能。不仅如此,我已经把它改成了对所有View都通用!可以随心所欲使用这两个功能~~ 我做了一个大集合的demo,实现了ListView、GridView、ExpandableListView、ScrollView、WebView、ImageView、TextView的下拉刷新和上拉加载。后面会提供demo的下载地址。(csdn上的demo有小bug,最新代码已上传到github:https://github.com/jingchenUSTC/PullToRefreshAndLoad) 依照惯例,下面将会是一大波效果图: demo首页也是可下拉的ListView,在底下可以加入table: ListView: GridView: ExpandableListView: ScrollView: WebView: ImageView: TextView: 很不错吧?最后的ImageView和TextView是最简单的,直接在下面的接口方法里返回true。 增加上拉加载很简单,和管理下拉头一样,再多管理一个上拉头,也不费事;至于把它改成通用的就需要统一一下View的行为了,为此,我定义了这样一个接口: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38868463#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38868463#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.pulltorefresh.pullableview; - - <span class="keyword">public</span> <span class="keyword">interface</span> Pullable - { - <span class="comment">/**</span> - <span class="comment"> * 判断是否可以下拉,如果不需要下拉功能可以直接return false</span> - <span class="comment"> * </span> - <span class="comment"> * @return true如果可以下拉否则返回false</span> - <span class="comment"> */</span> - <span class="keyword">boolean</span> canPullDown(); - - <span class="comment">/**</span> - <span class="comment"> * 判断是否可以上拉,如果不需要上拉功能可以直接return false</span> - <span class="comment"> * </span> - <span class="comment"> * @return true如果可以上拉否则返回false</span> - <span class="comment"> */</span> - <span class="keyword">boolean</span> canPullUp(); - } 从 接口名就可以看出它是一个提供判断是否可拉的方法的接口。这个接口的两个方法,canPullDown()是判断何时可以下拉的方 法,canPullUp()则是判断何时可以上拉,我在demo中的判断是滑到顶部的时候可以下拉,滑到底部的时候可以上拉。所有需要上拉和下拉的 View都需要实现这个接口。后面会给出一些View的实现。先来看看改进后的自定义的布局PullToRefreshLayout,增加了一个上拉头, 下拉头和上拉头之间的View是实现了Pullable接口的pullableView。相比前面的版本,这里有改动的需要注意的地方如下: ...

2015年6月12日 · 18 分钟 · 天边的星星

Android动画机制全解析

导论 本文着重讲解Android3.0后推出的属性动画框架Property Animation——Animator。 产生原因 3.0之前已有的动画框架——Animation存在一些局限性, Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件。 而在Animator框架中使用最多的是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值,多个ObjectAnimator组合到AnimatorSet形成一个动画。而且ObjectAnimator能够自动驱动,可以调用setFrameDelay(longframeDelay)设置动画帧之间的间隙时间,调整帧率,减少动画过程中频繁绘制界面,而在不影响动画效果的前提下减少CPU资源消耗。因此,Anroid推出的强大的属性动画框架,基本可以实现所有的动画效果。 强大的原因 因为属性动画框架操作的是真实的属性值,直接变化了对象的属性,因此可以很灵活的实现各种效果,而不局限于以前的4种动画效果。 ObjectAnimator ObjectAnimator是属性动画框架中最重要的实行类,创建一个ObjectAnimator只需通过他的静态工厂类直接返回一个ObjectAnimator对象。传的参数包括一个对象和对象的属性名字,但这个属性必须有get和set函数,内部会通过java反射机制来调用set函数修改对象属性值。还包括属性的初始值,最终值,还可以调用setInterpolator设置曲线函数。 ObjectAnimator实例 [view plain](http://www.netfoucs.com/article/x359981514/92198.html#)[copy to clipboard](http://www.netfoucs.com/article/x359981514/92198.html#)[print](http://www.netfoucs.com/article/x359981514/92198.html#)[?](http://www.netfoucs.com/article/x359981514/92198.html#) - ObjectAnimator.ofFloat(view, <span class="string">&#8220;rotationX&#8221;</span>, <span class="number"></span>.0F, <span class="number">360</span>.0F).setDuration(<span class="number">1000</span>).start(); 这个例子很简单,针对view的属性rotationX进行持续时间为1000ms的0到360的角度变换。 PS:可操纵的属性参数:x/y;scaleX/scaleY;rotationX/ rotationY;transitionX/ transitionY等等。 PS:X是View最终的位置、translationX为最终位置与布局时初始位置的差。所以若就用translationX即为在原来基础上移动多少,X为最终多少。getX()的值为getLeft()与getTranslationX()的和。 动画绘制过程的监听 [view plain](http://www.netfoucs.com/article/x359981514/92198.html#)[copy to clipboard](http://www.netfoucs.com/article/x359981514/92198.html#)[print](http://www.netfoucs.com/article/x359981514/92198.html#)[?](http://www.netfoucs.com/article/x359981514/92198.html#) - animator.addUpdateListener(<span class="keyword">new</span> AnimatorUpdateListener() { <span class="annotation">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> onAnimationUpdate(ValueAnimator arg0) { } }); 该方法用来监听动画绘制过程中的每一帧的改变,通过这个方法,我们可以在动画重绘的过程中,实现自己的逻辑。 同时修改多个属性值 当然这个可以使用Animationset来实现,这里我们使用一种取巧的方法来实现: [view plain](http://www.netfoucs.com/article/x359981514/92198.html#)[copy to clipboard](http://www.netfoucs.com/article/x359981514/92198.html#)[print](http://www.netfoucs.com/article/x359981514/92198.html#)[?](http://www.netfoucs.com/article/x359981514/92198.html#) - ObjectAnimator anim = ObjectAnimator.ofFloat(view, <span class="string">&#8220;xxx&#8221;</span>, <span class="number">1</span>.0F, <span class="number"></span>.0F) .setDuration(<span class="number">500</span>); anim.start(); anim.addUpdateListener(<span class="keyword">new</span> AnimatorUpdateListener() { <span class="annotation">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> onAnimationUpdate(ValueAnimator animation) { floatcVal = (Float) animation.getAnimatedValue(); view.setAlpha(cVal); view.setScaleX(cVal); view.setScaleY(cVal); } }); 我们可以监听一个并不存在的属性,而在监听动画更新的方法中,去修改view的属性,监听一个不存在的属性的原因就是,我们只需要动画的变化值,通过这个值,我们自己来实现要修改的效果,实际上,更直接的方法,就是使用ValueAnimator来实现,其实ObjectAnimator就是ValueAnimator的子类,这个在下面会具体讲到。 ...

2015年6月11日 · 8 分钟 · 天边的星星

Material Design开发利器

Android 5.0 Lollipop 是迄今为止最重大的一次发布,很大程度上是因为 material design —— 这是一门新的设计语言,它刷新了整个 Android 的用户体验。但是对于开发者来说,要设计出完全符合 material design 哲学的应用,是一个很大的挑战。Android Design Support Library 对此提供了很好的支持,里面汇集了很多重要的 material design 控件,支持所有 Android 2.1 及后续版本。里面你可以看到 navigation drawer view、floating labels、floating action button、snackbar、tabs,以及一套将它们紧密结合在一起的动作与滚动框架。 Navigation View(导航视图) 无论从应用标识、内容导航,还是设计一致性来讲,navigation drawer 都是首当其冲的焦点。现在,NavigationView 让导航栏变得更简单,它提供了 navigation drawer 需要的框架,以及通过资源文件来自定义更多菜单项的能力。 ![navigationview](http://ac-lhzo7z96.clouddn.com/1433285108918) 你只需要将 NavigationView 作为 DrawerLayout 的内容视图来使用即可,例如: ![drawerlayout](http://ac-lhzo7z96.clouddn.com/1433297055793) 这里你会注意到两个属性:app:heanderLaytout 用来控制 header 部分的布局;app:menu 指定了菜单资源。NavigationView 自动处理了状态栏的变化,保证可以在 API 21+ 的设备上正确运行。 最简单的 drawer 菜单就是一个允许选择的菜单项集合,例如: ![simplemenu](http://ac-lhzo7z96.clouddn.com/1433297095098) 选中的菜单会高亮显示,以提醒用户当前选择的是哪个菜单项。 你也可以在菜单中使用 subheader 来实现独立的分组: ![subheader_menu](http://ac-lhzo7z96.clouddn.com/1433297134486) 调用 setNavigationItemSelectedListener() 后,在菜单项被选中的时候,你会通过OnNavigationItemSelectedListener 得到回调。在处理回调时,你会知道是哪个菜单项被点击,此时你可以处理选择事件,修改选中状态,加载新的内容,以及通过代码来关闭 drawer,或者其他任何你想执行的操作。 文字输入时的悬浮标签 尽管 EditText 已经为 material design 做了一些改善,但是还不够,譬如它在我们输入第一个字符的时候,就会自动隐藏掉提示标签。现在你该使用 TextInputLayout 了,它会在用户开始输入之后,自动将提示标签悬浮到 EditText 上方,这样用户就永远都能知道输入内容的上下文。 ...

2015年6月6日 · 2 分钟 · 天边的星星

Android应用层View绘制流程与源码分析

1 背景 还记得前面《Android应用setContentView与LayoutInflater加载解析机制源码分析》这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现。 前面《Android触摸屏事件派发机制详解与源码分析一(View篇)》文章的3-1小节说过Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View实现的,当然也包括我们后面一步一步引出的自定义控件也不例外,所以说这些View应该都具有相同的绘制流程与机制才能显示到屏幕上(因为他们都具备相同的父类View,可能每个控件的具体绘制逻辑有差异,但是主流程都是一样的)。经过总结发现每一个View的绘制过程都必须经历三个最主要的过程,也就是measure、layout和draw。 既然一个View的绘制主要流程是这三步,那一定有一个开始地方呀,就像一个类从main函数执行一样呀。对于View的绘制开始调运地方这里先给出结论,本文后面会反过来分析原因的,先往下看就行。具体结论如下: 整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法(这个方法巨长)开始的,该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个,如下: `&lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">performTraversals&lt;/span>() { ...... &lt;span class="hljs-comment">//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来&lt;/span> &lt;span class="hljs-comment">//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); &lt;span class="hljs-keyword">int&lt;/span> childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(&lt;span class="hljs-number">0&lt;/span>, &lt;span class="hljs-number">0&lt;/span>, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... mView.draw(canvas); ...... }` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ` &lt;span class="hljs-javadoc">/** * Figures out the measure spec for the root view in a window based on it's * layout params. * *&lt;span class="hljs-javadoctag"> @param&lt;/span> windowSize * The available width or height of the window * *&lt;span class="hljs-javadoctag"> @param&lt;/span> rootDimension * The layout params for one dimension (width or height) of the * window. * *&lt;span class="hljs-javadoctag"> @return&lt;/span> The measure spec to use to measure the root view. */&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> &lt;span class="hljs-title">getRootMeasureSpec&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> windowSize, &lt;span class="hljs-keyword">int&lt;/span> rootDimension) { &lt;span class="hljs-keyword">int&lt;/span> measureSpec; &lt;span class="hljs-keyword">switch&lt;/span> (rootDimension) { &lt;span class="hljs-keyword">case&lt;/span> ViewGroup.LayoutParams.MATCH_PARENT: &lt;span class="hljs-comment">// Window can't resize. Force root view to be windowSize.&lt;/span> measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); &lt;span class="hljs-keyword">break&lt;/span>; ...... } &lt;span class="hljs-keyword">return&lt;/span> measureSpec; }` 1 ...

2015年6月6日 · 19 分钟 · 天边的星星

Android自定义控件(状态提示图表)

1 背景 前面分析那么多系统源码了,也该暂停下来休息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源码后的实例演练—-自定义控件。 这个实例很适合新手入门自定义控件。先看下效果图: 横屏模式如下: 竖屏模式如下: 看见没有,这个控件完全自定义的,连文字等都是自定义的,没有任何图片等资源,就仅仅是一个小的java文件,这个界面只有一个控件。如下咱们看下实现代码。 !!!!!!! 下载Demo工程源码点击我 【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】 2 实例代码 如下就是整个工程的源码了。 自定义上面展示的控件AreaChartsView源码: `&lt;span class="hljs-javadoc">/** * Author : yanbo * Date : 2015-06-03 * Time : 09:22 * Description : 自定义区域描述图表View */&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-class">&lt;span class="hljs-keyword">class&lt;/span> &lt;span class="hljs-title">AreaChartsView&lt;/span> &lt;span class="hljs-keyword">extends&lt;/span> &lt;span class="hljs-title">View&lt;/span> {&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> Paint mPaint; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mZeroPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mMaxYPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mMaxXPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mWidth, mHight; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mRealWidth, mRealHight; &lt;span class="hljs-keyword">private&lt;/span> String mTitleY, mTitleX; &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mXLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mYLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;String&gt; mGridLevelText = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mGridColorLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mGridTxtColorLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mGridLevel = mXLevel.size() - &lt;span class="hljs-number">1&lt;/span>; &lt;span class="hljs-comment">//title字符大小&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mXYTitleTextSize = &lt;span class="hljs-number">40&lt;/span>; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mMeasureXpos, mMeasureYpos; &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-title">AreaChartsView&lt;/span>(Context context, AttributeSet attrs) { &lt;span class="hljs-keyword">super&lt;/span>(context, attrs); mPaint = &lt;span class="hljs-keyword">new&lt;/span> Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setAntiAlias(&lt;span class="hljs-keyword">true&lt;/span>); mPaint.setFilterBitmap(&lt;span class="hljs-keyword">true&lt;/span>); } &lt;span class="hljs-annotation">@Override&lt;/span> &lt;span class="hljs-keyword">protected&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onLayout&lt;/span>(&lt;span class="hljs-keyword">boolean&lt;/span> changed, &lt;span class="hljs-keyword">int&lt;/span> left, &lt;span class="hljs-keyword">int&lt;/span> top, &lt;span class="hljs-keyword">int&lt;/span> right, &lt;span class="hljs-keyword">int&lt;/span> bottom) { &lt;span class="hljs-keyword">super&lt;/span>.onLayout(changed, left, top, right, bottom); mWidth = getWidth(); mHight = getHeight(); } &lt;span class="hljs-annotation">@Override&lt;/span> &lt;span class="hljs-keyword">protected&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onDraw&lt;/span>(Canvas canvas) { &lt;span class="hljs-keyword">super&lt;/span>.onDraw(canvas); initPosition(); drawXYTitle(canvas); drawXYLine(canvas); drawContent(canvas); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initPosition&lt;/span>() { &lt;span class="hljs-comment">//初始化坐标图的xy交点原点坐标&lt;/span> mZeroPos[&lt;span class="hljs-number">0&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; mZeroPos[&lt;span class="hljs-number">1&lt;/span>] = mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>; &lt;span class="hljs-comment">//初始化坐标图的X轴最大值坐标&lt;/span> mMaxXPos[&lt;span class="hljs-number">0&lt;/span>] = mWidth; mMaxXPos[&lt;span class="hljs-number">1&lt;/span>] = mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>; &lt;span class="hljs-comment">//初始化坐标图的Y轴最大值坐标&lt;/span> mMaxYPos[&lt;span class="hljs-number">0&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawXYTitle&lt;/span>(Canvas canvas) { mPaint.setColor(Color.parseColor(&lt;span class="hljs-string">"#1FB0E7"&lt;/span>)); mPaint.setTextSize(mXYTitleTextSize); mPaint.setTextAlign(Paint.Align.LEFT); &lt;span class="hljs-comment">//画Y轴顶的title&lt;/span> canvas.drawText(mTitleY, mMaxYPos[&lt;span class="hljs-number">0&lt;/span>] - mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>, mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] - mXYTitleTextSize, mPaint); mPaint.setTextAlign(Paint.Align.RIGHT); &lt;span class="hljs-comment">//画X轴顶的title&lt;/span> canvas.drawText(mTitleX, mMaxXPos[&lt;span class="hljs-number">0&lt;/span>], mMaxXPos[&lt;span class="hljs-number">1&lt;/span>] + mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>, mPaint); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawXYLine&lt;/span>(Canvas canvas) { mPaint.setColor(Color.DKGRAY); mPaint.setTextAlign(Paint.Align.RIGHT); &lt;span class="hljs-comment">//画XY轴&lt;/span> canvas.drawLine(mMaxYPos[&lt;span class="hljs-number">0&lt;/span>], mMaxYPos[&lt;span class="hljs-number">1&lt;/span>], mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); canvas.drawLine(mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>], mMaxXPos[&lt;span class="hljs-number">0&lt;/span>], mMaxXPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawContent&lt;/span>(Canvas canvas) { mGridLevel = mXLevel.size() - &lt;span class="hljs-number">1&lt;/span>; &lt;span class="hljs-comment">//计算出偏移title等显示尺标后的真实XY轴长度,便于接下来等分&lt;/span> mRealWidth = (mWidth - mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>); mRealHight = (mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>); &lt;span class="hljs-comment">//算出等分间距&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> offsetX = mRealWidth/(mGridLevel); &lt;span class="hljs-keyword">int&lt;/span> offsetY = mRealHight/(mGridLevel+&lt;span class="hljs-number">1&lt;/span>); &lt;span class="hljs-comment">//循环绘制content&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel+&lt;span class="hljs-number">1&lt;/span>; index++) { mPaint.setColor(Color.DKGRAY); mPaint.setTextAlign(Paint.Align.RIGHT); mPaint.setTextSize(mXYTitleTextSize-&lt;span class="hljs-number">5&lt;/span>); &lt;span class="hljs-comment">//绘制X轴的那些坐标区间点,包含0点坐标&lt;/span> canvas.drawText(String.valueOf(mXLevel.get(index)), mZeroPos[&lt;span class="hljs-number">0&lt;/span>]+(index*offsetX), mZeroPos[&lt;span class="hljs-number">1&lt;/span>] + mXYTitleTextSize, mPaint); &lt;span class="hljs-keyword">if&lt;/span> (index != &lt;span class="hljs-number">0&lt;/span>) { &lt;span class="hljs-comment">//绘制Y轴坐标区间点,不包含0点坐标,X轴已经画过了&lt;/span> canvas.drawText(String.valueOf(mYLevel.get(index)), mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>]-(index*offsetY), mPaint); } &lt;span class="hljs-keyword">if&lt;/span> (index == mGridLevel) { &lt;span class="hljs-comment">//坐标区间 = 真实区间 + 1&lt;/span> &lt;span class="hljs-keyword">break&lt;/span>; } mPaint.setColor(mGridColorLevel.get(mGridLevel - &lt;span class="hljs-number">1&lt;/span> - index)); mPaint.setStyle(Paint.Style.FILL); &lt;span class="hljs-comment">//绘制区间叠加图谱方块,从远到0坐标,因为小的图会覆盖大的图&lt;/span> canvas.drawRect(mMaxYPos[&lt;span class="hljs-number">0&lt;/span>], mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] + index*offsetY, mMaxXPos[&lt;span class="hljs-number">0&lt;/span>]-index*offsetX, mMaxXPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); mPaint.setColor(mGridTxtColorLevel.get(index)); mPaint.setTextAlign(Paint.Align.RIGHT); mPaint.setTextSize(mXYTitleTextSize); &lt;span class="hljs-comment">//绘制每个方块状态区间的提示文字&lt;/span> canvas.drawText(mGridLevelText.get(index), mMaxXPos[&lt;span class="hljs-number">0&lt;/span>] - index * offsetX - mXYTitleTextSize, mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] + index * offsetY + mXYTitleTextSize, mPaint); } &lt;span class="hljs-comment">//绘制当前坐标&lt;/span> drawNotice(canvas, offsetX, offsetY); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawNotice&lt;/span>(Canvas canvas, &lt;span class="hljs-keyword">int&lt;/span> offsetX, &lt;span class="hljs-keyword">int&lt;/span> offsetY) { &lt;span class="hljs-keyword">int&lt;/span> realPosX = &lt;span class="hljs-number">0&lt;/span>; &lt;span class="hljs-keyword">int&lt;/span> realPosY = &lt;span class="hljs-number">0&lt;/span>; &lt;span class="hljs-comment">//计算传入的x值与真实屏幕坐标的像素值的百分比差值转换&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel; index++) { &lt;span class="hljs-keyword">if&lt;/span> (mMeasureXpos &gt;= mXLevel.get(index) && mMeasureXpos &lt; mXLevel.get(index+&lt;span class="hljs-number">1&lt;/span>)) { &lt;span class="hljs-keyword">int&lt;/span> subValue = mMeasureXpos - mXLevel.get(index); &lt;span class="hljs-keyword">int&lt;/span> offset = mXLevel.get(index+&lt;span class="hljs-number">1&lt;/span>) - mXLevel.get(index); realPosX = mZeroPos[&lt;span class="hljs-number">0&lt;/span>] + index*offsetX + (subValue / offset); &lt;span class="hljs-keyword">break&lt;/span>; } } &lt;span class="hljs-comment">//计算传入的y值与真实屏幕坐标的像素值的百分比差值转换&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel; index++) { &lt;span class="hljs-keyword">if&lt;/span> (mMeasureYpos &gt;= mYLevel.get(index) && mMeasureYpos &lt; mYLevel.get(index+&lt;span class="hljs-number">1&lt;/span>)) { &lt;span class="hljs-keyword">int&lt;/span> subValue = mMeasureYpos - mYLevel.get(index); &lt;span class="hljs-keyword">int&lt;/span> offset = mYLevel.get(index+&lt;span class="hljs-number">1&lt;/span>) - mYLevel.get(index); realPosY = mZeroPos[&lt;span class="hljs-number">1&lt;/span>] - index*offsetY - (offsetY - (subValue / offset)); &lt;span class="hljs-keyword">break&lt;/span>; } } &lt;span class="hljs-comment">//画我们传入的坐标点的标记小红点&lt;/span> mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(realPosX, realPosY, &lt;span class="hljs-number">8&lt;/span>, mPaint); &lt;span class="hljs-keyword">int&lt;/span>[] centerPos = {mZeroPos[&lt;span class="hljs-number">0&lt;/span>] + mRealWidth/&lt;span class="hljs-number">2&lt;/span>, mZeroPos[&lt;span class="hljs-number">1&lt;/span>] - mRealHight/&lt;span class="hljs-number">2&lt;/span>}; mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); RectF rectF = &lt;span class="hljs-keyword">null&lt;/span>; Path path = &lt;span class="hljs-keyword">new&lt;/span> Path(); &lt;span class="hljs-comment">//画红点旁边的提示框和文字,有四个区域,然后提示框的小三角指标方位不同&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &lt;= centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &gt;= centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//left-bottom&lt;/span> &lt;span class="hljs-comment">//画三角形&lt;/span> path.moveTo(realPosX+&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY-&lt;span class="hljs-number">15&lt;/span>); &lt;span class="hljs-comment">//画矩形背景&lt;/span> rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY-&lt;span class="hljs-number">40&lt;/span>, realPosX+&lt;span class="hljs-number">200&lt;/span>, realPosY + &lt;span class="hljs-number">30&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); &lt;span class="hljs-comment">//画提示框的文字&lt;/span> mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX+&lt;span class="hljs-number">30&lt;/span>, realPosY, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &lt;= centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &lt; centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//left-top&lt;/span> path.moveTo(realPosX+&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX + &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">20&lt;/span>, realPosX+&lt;span class="hljs-number">200&lt;/span>, realPosY + &lt;span class="hljs-number">50&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX+&lt;span class="hljs-number">30&lt;/span>, realPosY+&lt;span class="hljs-number">20&lt;/span>, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &gt; centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &gt;= centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//right-bottom&lt;/span> path.moveTo(realPosX-&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX - &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX-&lt;span class="hljs-number">200&lt;/span>, realPosY-&lt;span class="hljs-number">40&lt;/span>, realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY + &lt;span class="hljs-number">30&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX-&lt;span class="hljs-number">180&lt;/span>, realPosY, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &gt; centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &lt; centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//right-top&lt;/span> path.moveTo(realPosX-&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX - &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX-&lt;span class="hljs-number">200&lt;/span>, realPosY - &lt;span class="hljs-number">20&lt;/span>, realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY + &lt;span class="hljs-number">50&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX-&lt;span class="hljs-number">180&lt;/span>, realPosY+&lt;span class="hljs-number">30&lt;/span>, mPaint); } path.close(); mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawPath(path, mPaint); } &lt;span class="hljs-comment">//设置当前比值&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">updateValues&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> x, &lt;span class="hljs-keyword">int&lt;/span> y) { mMeasureXpos = x; mMeasureYpos = y; postInvalidate(); } &lt;span class="hljs-comment">//设置XY轴顶角的title字体大小&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">setTitleTextSize&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> size) { mXYTitleTextSize = size; } &lt;span class="hljs-comment">//初始化X轴的坐标区间点值,可以不均等分&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initXLevelOffset&lt;/span>(ArrayList&lt;Integer&gt; list) { mXLevel.clear(); mXLevel.addAll(list); } &lt;span class="hljs-comment">//初始化Y轴的坐标区间点值,可以不均等分&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initYLevelOffset&lt;/span>(ArrayList&lt;Integer&gt; list) { mYLevel.clear(); mYLevel.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的提示文字,如果不想显示可以设置""&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridLevelText&lt;/span>(ArrayList&lt;String&gt; list) { mGridLevelText.clear(); mGridLevelText.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的颜色&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridColorLevel&lt;/span>(ArrayList&lt;Integer&gt; list) { mGridColorLevel.clear(); mGridColorLevel.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的提示文字颜色&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridTxtColorLevel&lt;/span>(ArrayList&lt;Integer&gt; list) { mGridTxtColorLevel.clear(); mGridTxtColorLevel.addAll(list); } &lt;span class="hljs-comment">//初始化XY轴title&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initTitleXY&lt;/span>(String x, String y) { mTitleX = x; mTitleY = y; } }` 再来看下布局文件: ...

2015年6月6日 · 7 分钟 · 天边的星星

Android屏幕适配全攻略(最权威的官方适配指导)

转载:http://blog.csdn.net/zhaokaiqiang1992 Android的屏幕适配一直以来都在折磨着我们这些开发者,本篇文章以Google的官方文档为基础,全面而深入的讲解了Android屏幕适配的原因、重要概念、解决方案及最佳实践,我相信如果你能认真的学习本文,对于Android的屏幕适配,你将有所收获! - [Android屏幕适配出现的原因](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#android屏幕适配出现的原因) - [重要概念](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#重要概念) - [屏幕尺寸](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#屏幕尺寸) - [屏幕分辨率](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#屏幕分辨率) - [屏幕像素密度](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#屏幕像素密度) - [dpdipdpisppx](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#dpdipdpisppx) - [mdpihdpixdpixxdpi](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#mdpihdpixdpixxdpi) - [解决方案](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#解决方案) - [支持各种屏幕尺寸](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#支持各种屏幕尺寸) - [使用wrap_contentmatch_parentweight](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用wrapcontentmatchparentweight) - [使用相对布局禁用绝对布局](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用相对布局禁用绝对布局) - [使用限定符](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用限定符) - [使用尺寸限定符](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用尺寸限定符) - [使用最小宽度限定符](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用最小宽度限定符) - [使用布局别名](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用布局别名) - [使用屏幕方向限定符](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用屏幕方向限定符) - [使用自动拉伸位图](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用自动拉伸位图) - [支持各种屏幕密度](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#支持各种屏幕密度) - [使用非密度制约像素](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#使用非密度制约像素) - [提供备用位图](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#提供备用位图) - [实施自适应用户界面流程](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#实施自适应用户界面流程) - [确定当前布局](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#确定当前布局) - [根据当前布局做出响应](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#根据当前布局做出响应) - [重复使用其他活动中的片段](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#重复使用其他活动中的片段) - [处理屏幕配置变化](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#处理屏幕配置变化) - [最佳实践](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#最佳实践) - [关于高清设计图尺寸](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#关于高清设计图尺寸) - [ImageView的ScaleType属性](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#imageview的scaletype属性) - [动态设置](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#动态设置) - [更多参考资料](http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023#更多参考资料) Android屏幕适配出现的原因 在我们学习如何进行屏幕适配之前,我们需要先了解下为什么Android需要进行屏幕适配。 由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,修改成他们想要的样子。 但是这种“碎片化”到底到达什么程度呢? 在2012年,OpenSignalMaps(以下简称OSM)发布了第一份Android碎片化报告,统计数据表明, 2012年,支持Android的设备共有3997种。 2013年,支持Android的设备共有11868种。 2014年,支持Android的设备共有18796种。 下面这张图片所显示的内容足以充分说明当今Android系统碎片化问题的严重性,因为该图片中的每一个矩形都代表着一种Android设备。 ...

2015年6月6日 · 16 分钟 · 天边的星星

通过 Navigation View 创建导航抽屉

随着 Google I/O 2015,新的 Android Design Support Library 也出现了。 Android Design Support Library 给开发者带来了一些重要的 Material Design 组件,并且向下兼容到 Android 2.1,Navigation View就是其中之一,可用于方便地创建导航抽屉。 效果如下: 因为需要向下兼容,所以以下内容的 Activity 均继承于AppCompatActivity,使用的主题的父主题均为 AppCompat 的主题。 基本步骤 通过Navigation View创建导航抽屉首先自然需要引入这个支持包: compile 'com.android.support:design:22.2.0' 布局文件中加入以下代码: ``` <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <span class="c">&lt;!-- 需要呈现的内容 --&gt;</span> <span class="nt">&lt;android.support.design.widget.NavigationView</span> <span class="na">android:layout_width=</span><span class="s">"wrap_content"</span> <span class="na">android:layout_height=</span><span class="s">"match_parent"</span> <span class="na">android:layout_gravity=</span><span class="s">"start"</span> <span class="na">app:headerLayout=</span><span class="s">"@layout/drawer_header"</span> <span class="na">app:menu=</span><span class="s">"@menu/drawer"</span><span class="nt">/&gt;</span> </android.support.v4.widget.DrawerLayout> </div> 其中`app:headerLayout`用于指定一个任意的布局,作为导航抽屉的顶部,如效果图中的紫色带 Username 字样部分。 而`app:menu`用于指定导航抽屉的菜单项,具体代码如下: <div class="codehilite"> <menu xmlns:android=“http://schemas.android.com/apk/res/android"> <span class="nt">&lt;group</span> <span class="na">android:checkableBehavior=</span><span class="s">"single"</span><span class="nt">&gt;</span> <span class="nt">&lt;item</span> <span class="na">android:id=</span><span class="s">"@+id/nav_home"</span> <span class="na">android:icon=</span><span class="s">"@drawable/ic_dashboard"</span> <span class="na">android:title=</span><span class="s">"Home"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;item</span> <span class="na">android:id=</span><span class="s">"@+id/nav_messages"</span> <span class="na">android:icon=</span><span class="s">"@drawable/ic_event"</span> <span class="na">android:title=</span><span class="s">"Messages"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;item</span> <span class="na">android:id=</span><span class="s">"@+id/nav_friends"</span> <span class="na">android:icon=</span><span class="s">"@drawable/ic_headset"</span> <span class="na">android:title=</span><span class="s">"Friends"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;item</span> <span class="na">android:id=</span><span class="s">"@+id/nav_discussion"</span> <span class="na">android:icon=</span><span class="s">"@drawable/ic_forum"</span> <span class="na">android:title=</span><span class="s">"Discussion"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/group&gt;</span> <span class="nt">&lt;item</span> <span class="na">android:title=</span><span class="s">"Sub items"</span><span class="nt">&gt;</span> <span class="nt">&lt;menu&gt;</span> <span class="nt">&lt;item</span> <span class="na">android:icon=</span><span class="s">"@drawable/ic_dashboard"</span> <span class="na">android:title=</span><span class="s">"Sub item 1"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;item</span> <span class="na">android:icon=</span><span class="s">"@drawable/ic_forum"</span> <span class="na">android:title=</span><span class="s">"Sub item 2"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/menu&gt;</span> <span class="nt">&lt;/item&gt;</span> </menu> ...

2015年6月6日 · 2 分钟 · 天边的星星

Android开发技术周报

教程 在 Android 中使用 data-binder 绑定布局 xml 与数据在前几天的 Google IO 2015 中,Google 在 support-v7 中新增了 data-binder,使用 data-binder 可以直接在布局的 xml 中绑定布局与数据,从而简化代码。因为 data-binder 是包含在 support-v7 包里面的,所以可以向下兼容到最低 Android 2.1 (API level 7+). Android的材料设计兼容库这个兼容库很容易和之前的 Android Support Library 22.1混淆,都是兼容库,区别是这个库多了个Design。 Android Support Library 22.1只是支持了一些基本控件的材料设计化,但是这个库更多的是对一些特效的实现,这个库和github上的很多开源项目是有很大关系的,material design的很多效果,同一种效果在github上有太多的实现,现在官方把部分效果标准化了。 Google I/O 2015 为 Android 开发者带来了哪些福利?先得说的便是,今年的更新有些不给力,至少显得不够 Geek。我也不打算接着盘点一些在 Keynote 中的资讯,想必很多人在各个站点已经看过不知道多少遍了,我接下来想说的一些是关于这次 Google I/O 为 Android 开发者们带来了怎样的福利。 Nexus6 With Android M开启多窗口模式昨天的Google IO之后,Google放出了Android M Preview for Nexus6. 固件大家可以去Google的官网去下,下好了刷完之后,就可以体验一下最新的Android M了。 使用Android Accessibility实现免Root自动批量安装功能对于国内Android设备,应用的自动批量安装/更新一直是一个痛点,在之前,第三方应用商店通常要求设备Root,然后调用系统的PackageManagerService命令行来实现后台安装。最近,豌豆荚利用Android Accessibility(辅助功能)在业内率先实现了免Root自动批量安装功能。 Android 9patch 图片解析堆溢出漏洞分析前谷歌公开了一个今年1月份更新的漏洞。这个漏洞修复了一个存在于Android 5.1版本以下图片渲染的问题,可以查看相关链接。9patch是Android上特有的一种图片格式,就是在普通的png图片的基础了增加了一些像素的边框,使之具有可随意拉伸、缩放的功能。 Gradle 修改 Maven 仓库地址要先说明的是本文说的“渠道”单指在AndroidManifest.xml 用定义的一个标识字符串(如友盟统计)。在代码或者通过其他文件定义的方式殊途同归。说起 Android 多渠道打包,真是八仙过海各显神通:有手动一个个耐心打包的,有用Ant或Maven重复跑编译任务的,有用apktool解包后再修改重打包的,有在build.gradle定义一堆flavor的,乃至有通过apk里META-INF/下的空文件来定义渠道的。 谷歌推荐的技术能力提升指南打好扎实的计算机科学基础对于成为一个成功的软件工程师是非常重要的。本指南主要关于如何提升自己的技术能力,非常适合学生用于制定教学课程,当然这里提供的网络资源,并不意味着就可以完全取代现有的课程,正式的课程安排还是要学的(除非你不想拿到毕业证书)。 Android应用setContentView与LayoutInflater加载解析机制源码分析其实之所以要说这个话题有几个原因:1.理解xml等控件是咋被显示的原理,通常大家写代码都是直接在onCreate里setContentView就完事,没怎么关注其实现原理。2.前面分析《Android触摸屏事件派发机制详解与源码分析三(Activity篇)》时提到了一些关于布局嵌套的问题,当时没有深入解释。 Android应用程序UI硬件加速渲染的预加载资源地图集服务(Asset Atlas Service)分析我们知道,Android系统在启动的时候,会对一些系统资源进行预加载。这样不仅使得应用程序在需要时可以快速地访问这些资源,还使得这些资源能够在不同应用程序之间进行共享。在硬件加速渲染环境中,这些预加载资源还有进一步优化的空间。 Android 不规则封闭区域填充 手指秒变油漆桶图像的填充有2种经典算法。 一种是种子填充法。种子填充法理论上能够填充任意区域和图形,但是这种算法存在大量的反复入栈和大规模的递归,降低了填充效率。 另一种是扫描线填充法。 Android屏幕适配全攻略(最权威的官方适配指导)Android的屏幕适配一直以来都在折磨着我们这些开发者,本篇文章以Google的官方文档为基础,全面而深入的讲解了Android屏幕适配的原因、重要概念、解决方案及最佳实践,我相信如果你能认真的学习本文,对于Android的屏幕适配,你将有所收获! Android系统Root与静默安装静默安装,指的是安装时无需任何用户干预,直接按默认设置安装应用。因为,它的无需用户干预,很多情况下变成了用户压根不知道,应用不知不觉就安装上了。是在推广上极为流氓的手段,很类似PC上的捆绑安装。正因为静默安装时极为流氓的推广行为,所以,其推广价格也极其高。 代码&开源库 cheesesquareAndroid Design library的示例。 Android-NiceTab支持小圆点,背景模糊,图标cross fade等效果的自定义Tab. BGARefreshLayout-Android多种下拉刷新效果、上拉加载更多、可配置自定义头部广告位。 AnimateCheckBox自定义CheckBox,选中未选中的切换动画很赞。 SelectorInjection一个强大的selector注入器,它可以让view自动产生selector状态,免去了你写selector的麻烦。 ShareLoginLib第三方分享登录组件. AndroidGradleTemplateGradle + Android Studio + Robolectric + Espresso + Mockito + EasyMock/PowerMock + JaCoCo Android-Task可以在后台执行Task的Library。 FORMWatchFaceAnddroid Wear 表盘。 MultiThreadDownloader逻辑比较简单但实用的Android多线程断点续传下载器。 DatePickerAndroid日历选择器。 Material Calendar ViewMaterial Design风格的日历控件。 工具 Android Studio 1.3 Preview1The new version contains many new features.: New Allocation Tracker New Heap dump Viewer Many new code inspections to enforce framework and support library threading annotations, range annotations, call super, check result, etc. Missing permission checks and unhandled revocable permission checks Android M preview data binding Support Support for adding Google Services to the project in the project structure dialog (especially for Analytics) SDK update notifications, and brand new integrated SDK manager UI New quickfixes, such as automatic generation of a Parcelable implementation Many built-in live code templates Many other smaller features and bug fixes As announced at Google I/O, Android Studio 1.3 will include C/C++ support as well, but that is not included in the first couple of preview buil * [Android NDK r10e][28]Release Notes: <http://developer.android.com/intl/zh-cn/ndk/downloads/index.html#rel> * [GsonFormat][29]根据Gson库使用的要求,将JSONObject格式的String 解析成实体的 Android Studio 插件。 ### 视频 {#} 1. [Google I/O 2015的各种视频][30]墙内Google I/O 2015的各种视频,没有看的小伙伴赶紧去瞅瞅看吧。 2. [Android QQ音乐架构演进][31]随着移动互联网的不断蓬勃发展,更多用户在移动设备上使用音乐服务,QQ音乐移动客户端使用用户数也在屡创新高,QQ音乐为了达到更好的用户体验并实现用户的新需求,原有的框架已经不能优雅的实现新需求和优先。如何优雅的实现各种需求并在性能和稳定性得到提高,QQ音乐Android开发团队通过以下的篇章给大家分享QQ音乐架构演进带来的痛与乐。 转自:http://androidweekly.cn/android-dev-weekly-issue33/?utm_source=tuicool

2015年6月6日 · 2 分钟 · 天边的星星

Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()

Android中触摸事件传递过程中最重要的是dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()方法。这个是困扰初学者的问题之一,我开始也是。这里记录一下dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()的处理过程,以供记忆。 dispatchTouchEvent是处理触摸事件分发,事件(多数情况)是从Activity的dispatchTouchEvent开始的。执行 super.dispatchTouchEvent(ev),事件向下分发。 onInterceptTouchEvent是ViewGroup提供的方法,默认返回false,返回true表示拦截。 onTouchEvent是View中提供的方法,ViewGroup也有这个方法,view中不提供onInterceptTouchEvent。view中默认返回true,表示消费了这个事件。 View里,有两个回调函数 : **[java]** [view plain](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[copy](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[print](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[?](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/219578)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/219578/fork) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="27" height="15" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">public</span> <span class="keyword">boolean</span> dispatchTouchEvent(MotionEvent ev); - <span class="keyword">public</span> <span class="keyword">boolean</span> onTouchEvent(MotionEvent ev); ViewGroup里,有三个回调函数 : **[java]** [view plain](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[copy](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[print](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[?](http://blog.csdn.net/xyz_lmn/article/details/12517911#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/219578)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/219578/fork) <div> <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="27" height="15" align="middle" name="ZeroClipboardMovie_2"> </embed> </div> </div> - <span class="keyword">public</span> <span class="keyword">boolean</span> dispatchTouchEvent(MotionEvent ev); - <span class="keyword">public</span> <span class="keyword">boolean</span> onInterceptTouchEvent(MotionEvent ev); - <span class="keyword">public</span> <span class="keyword">boolean</span> onTouchEvent(MotionEvent ev); 在Activity里,有两个回调函数 : ...

2015年6月1日 · 1 分钟 · 天边的星星