VX包介绍转自这里
1, Android Support V4, V7, V13是什么?
本质上就是三个java library。
2, 为什么要有support库?
如果在低版本Android平台上开发一个应用程序,而应用程序又想使用高版本才拥有的功能,就需要使用Support库。
3, 三个Support 库的区别和作用是什么?
Android Support v4 是最早(2011年4月份)实现的库。用在Android1.6 (API lever 4)或者更高版本之上。它包含了相对V4, V13大的多的功能。
例如:Fragment,NotificationCompat,LoadBroadcastManager,ViewPager,PageTabAtrip,Loader,FileProvider 等。
详细API 参考 http://developer.android.com/reference/android/support/v4/app/package-summary.html
Android Support v7: 这个包是为了考虑Android2.1(API level 7) 及以上版本而设计的,但是v7是要依赖v4这个包的,也就是如果要使用,两个包得同时 被引用。
v7支持了Action Bar。
Android Support v13:这个包的设计是为了android 3.2及更高版本的,一般我们都不常用,平板开发中能用到。
sdk\extras\android\support\samples\Support7Demos笔记
代码分析:
Logger包
关于log:需要使用Log输出一些信息。那么我们就需要使用到Log FrameWork。使用及注释如下
public void initializeLogging() {
//封装android底层log框架
LogWrapper logWrapper = new LogWrapper();
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
/** * Log的消息传递是依据一个链路依次传递的,Log传递到LogWrapper,LogWrapper传递到MessageOnlyLogFilter,MessageOnlyLogFilter传递到LogFragment */
Log.setLogNode(logWrapper);
// 过滤器
MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter();
logWrapper.setNext(msgFilter);
// On screen logging via a fragment with a TextView.
LogFragment logFragment = (LogFragment) getSupportFragmentManager()
.findFragmentById(R.id.log_fragment);
msgFilter.setNext(logFragment.getLogView());
Log.i(TAG, "Ready");
}
涉及到的主要的类:
package com.example.android.common.logger;
/** * Helper class for a list (or tree) of LoggerNodes. * * <p>When this is set as the head of the list, * an instance of it can function as a drop-in replacement for {@link android.util.Log}. * Most of the methods in this class server only to map a method call in Log to its equivalent * in LogNode.</p> */
public class Log {
// Grabbing the native values from Android's native logging facilities,
// to make for easy migration and interop.
public static final int NONE = -1;
public static final int VERBOSE = android.util.Log.VERBOSE;
public static final int DEBUG = android.util.Log.DEBUG;
public static final int INFO = android.util.Log.INFO;
public static final int WARN = android.util.Log.WARN;
public static final int ERROR = android.util.Log.ERROR;
public static final int ASSERT = android.util.Log.ASSERT;
// Stores the beginning of the LogNode topology.
private static LogNode mLogNode;
/** * Returns the next LogNode in the linked list. */
public static LogNode getLogNode() {
return mLogNode;
}
/** * Sets the LogNode data will be sent to. */
public static void setLogNode(LogNode node) {
mLogNode = node;
}
/** * Instructs the LogNode to print the log data provided. Other LogNodes can * be chained to the end of the LogNode as desired. * * @param priority Log level of the data being logged. Verbose, Error, etc. * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void println(int priority, String tag, String msg, Throwable tr) {
if (mLogNode != null) {
mLogNode.println(priority, tag, msg, tr);
}
}
/** * Instructs the LogNode to print the log data provided. Other LogNodes can * be chained to the end of the LogNode as desired. * * @param priority Log level of the data being logged. Verbose, Error, etc. * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. The actual message to be logged. */
public static void println(int priority, String tag, String msg) {
println(priority, tag, msg, null);
}
/** * Prints a message at VERBOSE priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void v(String tag, String msg, Throwable tr) {
println(VERBOSE, tag, msg, tr);
}
/** * Prints a message at VERBOSE priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. */
public static void v(String tag, String msg) {
v(tag, msg, null);
}
/** * Prints a message at DEBUG priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void d(String tag, String msg, Throwable tr) {
println(DEBUG, tag, msg, tr);
}
/** * Prints a message at DEBUG priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. */
public static void d(String tag, String msg) {
d(tag, msg, null);
}
/** * Prints a message at INFO priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void i(String tag, String msg, Throwable tr) {
println(INFO, tag, msg, tr);
}
/** * Prints a message at INFO priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. */
public static void i(String tag, String msg) {
i(tag, msg, null);
}
/** * Prints a message at WARN priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void w(String tag, String msg, Throwable tr) {
println(WARN, tag, msg, tr);
}
/** * Prints a message at WARN priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. */
public static void w(String tag, String msg) {
w(tag, msg, null);
}
/** * Prints a message at WARN priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void w(String tag, Throwable tr) {
w(tag, null, tr);
}
/** * Prints a message at ERROR priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void e(String tag, String msg, Throwable tr) {
println(ERROR, tag, msg, tr);
}
/** * Prints a message at ERROR priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. */
public static void e(String tag, String msg) {
e(tag, msg, null);
}
/** * Prints a message at ASSERT priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void wtf(String tag, String msg, Throwable tr) {
println(ASSERT, tag, msg, tr);
}
/** * Prints a message at ASSERT priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. */
public static void wtf(String tag, String msg) {
wtf(tag, msg, null);
}
/** * Prints a message at ASSERT priority. * * @param tag Tag for for the log data. Can be used to organize log statements. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
public static void wtf(String tag, Throwable tr) {
wtf(tag, null, tr);
}
}
/** 内部包装了android的Log FrameWork Helper class which wraps Android's native Log utility in the Logger interface. This way * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. */
public class LogWrapper implements LogNode {
// For piping: The next node to receive Log data after this one has done its work.
private LogNode mNext;
/** * Returns the next LogNode in the linked list. */
public LogNode getNext() {
return mNext;
}
/** * Sets the LogNode data will be sent to.. */
public void setNext(LogNode node) {
mNext = node;
}
/** * Prints data out to the console using Android's native log mechanism. * @param priority Log level of the data being logged. Verbose, Error, etc. * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
@Override
public void println(int priority, String tag, String msg, Throwable tr) {
// There actually are log methods that don't take a msg parameter. For now,
// if that's the case, just convert null to the empty string and move on.
String useMsg = msg;
if (useMsg == null) {
useMsg = "";
}
// If an exeption was provided, convert that exception to a usable string and attach
// it to the end of the msg method.
if (tr != null) {
msg += "\n" + Log.getStackTraceString(tr);
}
// This is functionally identical to Log.x(tag, useMsg);
// For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)
/** * 注意这里的Log是这个包下的package android.util */
Log.println(priority, tag, useMsg);
// If this isn't the last node in the chain, move things along.
if (mNext != null) {
mNext.println(priority, tag, msg, tr);
}
}
}
package com.example.android.common.logger;
/**
* Basic interface for a logging system that can output to one or more targets.
* Note that in addition to classes that will output these logs in some format,
* one can also implement this interface over a filter and insert that in the chain,
* such that no targets further down see certain data, or see manipulated forms of the data.
* You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data
* it received to HTML and sent it along to the next node in the chain, without printing it
* anywhere.
*/
public interface LogNode {
/**
* Instructs first LogNode in the list to print the log data provided.
* @param priority Log level of the data being logged. Verbose, Error, etc.
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged. The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public void println(int priority, String tag, String msg, Throwable tr);
}
package com.example.android.common.logger;
/** 除去一些用户并不关心的的数据 * Simple {@link LogNode} filter, removes everything except the message. * Useful for situations like on-screen log output where you don't want a lot of metadata displayed, * just easy-to-read message updates as they're happening. */
public class MessageOnlyLogFilter implements LogNode {
LogNode mNext;
/** * Takes the "next" LogNode as a parameter, to simplify chaining. * * @param next The next LogNode in the pipeline. */
public MessageOnlyLogFilter(LogNode next) {
mNext = next;
}
public MessageOnlyLogFilter() {
}
@Override
public void println(int priority, String tag, String msg, Throwable tr) {
if (mNext != null) {
getNext().println(Log.NONE, null, msg, null);
}
}
/** * Returns the next LogNode in the chain. */
public LogNode getNext() {
return mNext;
}
/** * Sets the LogNode data will be sent to.. */
public void setNext(LogNode node) {
mNext = node;
}
}
最终执行输出Log代码,上边各种封装类都是作为消息传递chain:
/** 展示Log的View Simple TextView which is used to output log data received through the LogNode interface. */
public class LogView extends TextView implements LogNode {
public LogView(Context context) {
super(context);
}
public LogView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LogView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/** * Formats the log data and prints it out to the LogView. * @param priority Log level of the data being logged. Verbose, Error, etc. * @param tag Tag for for the log data. Can be used to organize log statements. * @param msg The actual message to be logged. The actual message to be logged. * @param tr If an exception was thrown, this can be sent along for the logging facilities * to extract and print useful information. */
@Override
public void println(int priority, String tag, String msg, Throwable tr) {
String priorityStr = null;
// For the purposes of this View, we want to print the priority as readable text.
switch(priority) {
case android.util.Log.VERBOSE:
priorityStr = "VERBOSE";
break;
case android.util.Log.DEBUG:
priorityStr = "DEBUG";
break;
case android.util.Log.INFO:
priorityStr = "INFO";
break;
case android.util.Log.WARN:
priorityStr = "WARN";
break;
case android.util.Log.ERROR:
priorityStr = "ERROR";
break;
case android.util.Log.ASSERT:
priorityStr = "ASSERT";
break;
default:
break;
}
// Handily, the Log class has a facility for converting a stack trace into a usable string.
String exceptionStr = null;
if (tr != null) {
exceptionStr = android.util.Log.getStackTraceString(tr);
}
// Take the priority, tag, message, and exception, and concatenate as necessary
// into one usable line of text.
final StringBuilder outputBuilder = new StringBuilder();
String delimiter = "\t";
appendIfNotNull(outputBuilder, priorityStr, delimiter);
appendIfNotNull(outputBuilder, tag, delimiter);
appendIfNotNull(outputBuilder, msg, delimiter);
appendIfNotNull(outputBuilder, exceptionStr, delimiter);
// In case this was originally called from an AsyncTask or some other off-UI thread,
// make sure the update occurs within the UI thread.
((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() {
@Override
public void run() {
// Display the text we just generated within the LogView.
appendToLog(outputBuilder.toString());
}
})));
if (mNext != null) {
mNext.println(priority, tag, msg, tr);
}
}
public LogNode getNext() {
return mNext;
}
public void setNext(LogNode node) {
mNext = node;
}
/** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since * the logger takes so many arguments that might be null, this method helps cut out some of the * agonizing tedium of writing the same 3 lines over and over. * @param source StringBuilder containing the text to append to. * @param addStr The String to append * @param delimiter The String to separate the source and appended strings. A tab or comma, * for instance. * @return The fully concatenated String as a StringBuilder */
private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) {
if (addStr != null) {
if (addStr.length() == 0) {
delimiter = "";
}
return source.append(addStr).append(delimiter);
}
return source;
}
// The next LogNode in the chain.
LogNode mNext;
/** Outputs the string as a new line of log data in the LogView. */
public void appendToLog(String s) {
append("\n" + s);
}
}
floatingactionbuttonbasic包代码分析:
说明:FloatingActionButton 这个View其实类似于一个CheckBox,当点击的时候记录当前的状态(选中或没选中),根据当期的状态来改变背景图片。背景图片是一个Selecter,状态改变自己会切换图片。这样就基本实现了FloatingActionButton 。但是,还没有让View悬浮起来。这个是5.0的一个新特性。及支持设置View在Z轴的位置。android:elevation这个属性就是设置Z轴位置的。当Z轴位置大于0时那么这个View就悬浮在其他View的上边了。
/**
* A Floating Action Button is a {@link android.widget.Checkable} view distinguished by a circled
* icon floating above the UI, with special motion behaviors.
* 继承自FrameLayout,实现了Checkable接口
*/
public class FloatingActionButton extends FrameLayout implements Checkable {
/**
* Interface definition for a callback to be invoked when the checked state
* of a compound button changes.
*/
public static interface OnCheckedChangeListener {
/**
* Called when the checked state of a FAB has changed.
*
* @param fabView The FAB view whose state has changed.
* @param isChecked The new checked state of buttonView.
*/
void onCheckedChanged(FloatingActionButton fabView, boolean isChecked);
}
/**
* An array of states.
*/
private static final int[] CHECKED_STATE_SET = {
android.R.attr.state_checked
};
private static final String TAG = "FloatingActionButton";
// A boolean that tells if the FAB is checked or not.
private boolean mChecked;
// A listener to communicate that the FAB has changed it's state
private OnCheckedChangeListener mOnCheckedChangeListener;
public FloatingActionButton(Context context) {
this(context, null, 0, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr);
setClickable(true);
// Set the outline provider for this view. The provider is given the outline which it can
// then modify as needed. In this case we set the outline to be an oval fitting the height
// and width.
//给这个View设置outline provider。这个provider提供的outline是可以改变的。在这种情况下我们这支这个边框为填充高度和宽度
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, getWidth(), getHeight());
}
});
// Finally, enable clipping to the outline, using the provider we set above
//最后,设置一下使得outline可以点击
setClipToOutline(true);
}
/**
* Sets the checked/unchecked state of the FAB.
* @param checked
*/
public void setChecked(boolean checked) {
// If trying to set the current state, ignore.
if (checked == mChecked) {
return;
}
mChecked = checked;
// Now refresh the drawable state (so the icon changes)
refreshDrawableState();
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, checked);
}
}
/**
* Register a callback to be invoked when the checked state of this button
* changes.
*
* @param listener the callback to call on checked state change
*/
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
@Override
public boolean isChecked() {
return mChecked;
}
@Override
public void toggle() {
setChecked(!mChecked);
}
/**
* Override performClick() so that we can toggle the checked state when the view is clicked
* 当点击的时候会出发这个方法,super.performClick() 会抛出这个异常throw new RuntimeException("Stub!");打印Log
*/
@Override
public boolean performClick() {
toggle();
return super.performClick();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// As we have changed size, we should invalidate the outline so that is the the
// correct size
invalidateOutline();
}
/**
*
* @param extraSpace 如果不为extraSpace不为0,表示返回的int[]数组会在原始数组基础上添加额外的extraSpace个位置,在这些位置上你可以添加自己的一些额外信息
* @return
*/
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
/**
* mergeDrawableStates专门用来在onCreateDrawableState返回的数组上添加一个额外的属性
*/
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
}
package com.example.android.floatingactionbuttonbasic;
import com.example.android.common.logger.Log;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/** 这个Fragment将包含俩个FloatingActionButton,并监听点击事件。使用上边的Log包下的工具输出Log信息。 * This fragment inflates a layout with two Floating Action Buttons and acts as a listener to * changes on them. */
public class FloatingActionButtonBasicFragment extends Fragment implements FloatingActionButton.OnCheckedChangeListener{
private final static String TAG = "FloatingActionButtonBasicFragment";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fab_layout, container, false);
// Make this {@link Fragment} listen for changes in both FABs.
FloatingActionButton fab1 = (FloatingActionButton) rootView.findViewById(R.id.fab_1);
fab1.setOnCheckedChangeListener(this);
FloatingActionButton fab2 = (FloatingActionButton) rootView.findViewById(R.id.fab_2);
fab2.setOnCheckedChangeListener(this);
return rootView;
}
@Override
public void onCheckedChanged(FloatingActionButton fabView, boolean isChecked) {
// When a FAB is toggled, log the action.
switch (fabView.getId()){
case R.id.fab_1:
Log.d(TAG, String.format("FAB 1 was %s.", isChecked ? "checked" : "unchecked"));
break;
case R.id.fab_2:
Log.d(TAG, String.format("FAB 2 was %s.", isChecked ? "checked" : "unchecked"));
break;
default:
break;
}
}
}
使用:
package com.example.android.floatingactionbuttonbasic;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ViewAnimator;
import com.example.android.common.activities.SampleActivityBase;
import com.example.android.common.logger.Log;
import com.example.android.common.logger.LogFragment;
import com.example.android.common.logger.LogWrapper;
import com.example.android.common.logger.MessageOnlyLogFilter;
/** * A simple launcher activity containing a summary sample description, sample log and a custom * {@link android.support.v4.app.Fragment} which can display a view. * <p/> * For devices with displays with a width of 720dp or greater, the sample log is always visible, * on other devices it's visibility is controlled by an item on the Action Bar. */
public class MainActivity extends SampleActivityBase {
public static final String TAG = "MainActivity";
// Whether the Log Fragment is currently shown
private boolean mLogShown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
FloatingActionButtonBasicFragment fragment = new FloatingActionButtonBasicFragment();
transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_toggle_log:
mLogShown = !mLogShown;
ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output);
if (mLogShown) {
output.setDisplayedChild(1);
} else {
output.setDisplayedChild(0);
}
supportInvalidateOptionsMenu();
return true;
}
return super.onOptionsItemSelected(item);
}
/** * Create a chain of targets that will receive log data * 初始化Log工具 */
@Override
public void initializeLogging() {
//封装android底层log框架
LogWrapper logWrapper = new LogWrapper();
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
/** * Log的消息传递是依据一个链路依次传递的,Log传递到LogWrapper,LogWrapper传递到MessageOnlyLogFilter,MessageOnlyLogFilter传递到LogFragment */
Log.setLogNode(logWrapper);
// Filter strips out everything except the message text.
MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter();
logWrapper.setNext(msgFilter);
// On screen logging via a fragment with a TextView.
LogFragment logFragment = (LogFragment) getSupportFragmentManager()
.findFragmentById(R.id.log_fragment);
msgFilter.setNext(logFragment.getLogView());
Log.i(TAG, "Ready");
}
}
布局文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2014, The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License 这里还涉及到一些动画,这里不再说明。 -->
<FrameLayout android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" >
<!--elevation Z轴方向的距离 这个属性是android5.0系统支持的-->
<com.example.android.floatingactionbuttonbasic.FloatingActionButton android:id="@+id/fab_1" android:layout_width="@dimen/fab_size" android:layout_height="@dimen/fab_size" android:layout_marginTop="16dp" android:elevation="@dimen/fab_elevation" android:background="@drawable/fab_background" android:stateListAnimator="@animator/fab_anim" android:layout_gravity="center_horizontal">
<!--duplicateParentState表示当前View的DrawableState直接从其父View获取,而不是从他自己获取-->
<ImageView android:layout_width="@dimen/fab_icon_size" android:layout_height="@dimen/fab_icon_size" android:src="@drawable/fab_icons" android:layout_gravity="center" android:duplicateParentState="true"/>
</com.example.android.floatingactionbuttonbasic.FloatingActionButton>
<com.example.android.floatingactionbuttonbasic.FloatingActionButton android:id="@+id/fab_2" android:layout_width="@dimen/fab_size_small" android:layout_height="@dimen/fab_size_small" android:layout_marginTop="128dp" android:elevation="@dimen/fab_elevation" android:background="@drawable/fab_background" android:stateListAnimator="@animator/fab_anim" android:layout_gravity="center_horizontal">
<ImageView android:layout_width="@dimen/fab_icon_size" android:layout_height="@dimen/fab_icon_size" android:src="@drawable/fab_icons" android:layout_gravity="center" android:duplicateParentState="true"/>
</com.example.android.floatingactionbuttonbasic.FloatingActionButton>
</FrameLayout>
XML文件的预览效果:
代码下载需要5.0系统
效果图
代码分析:
Log输出同FloatingActionButton完全相同。这里不再分析。
View的创建过程分析:
package com.example.android.recyclerview; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.RadioButton; /** * 使用Fragment加载一个RecyclerView * Demonstrates the use of {@link RecyclerView} with a {@link LinearLayoutManager} and a * {@link GridLayoutManager}. */ public class RecyclerViewFragment extends Fragment { private static final String TAG = "RecyclerViewFragment"; private static final String KEY_LAYOUT_MANAGER = "layoutManager"; private static final int SPAN_COUNT = 2; private static final int DATASET_COUNT = 60; private enum LayoutManagerType { GRID_LAYOUT_MANAGER, LINEAR_LAYOUT_MANAGER } protected LayoutManagerType mCurrentLayoutManagerType; protected RadioButton mLinearLayoutRadioButton; protected RadioButton mGridLayoutRadioButton; protected RecyclerView mRecyclerView; protected CustomAdapter mAdapter; protected RecyclerView.LayoutManager mLayoutManager; protected String[] mDataset; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialize dataset, this data would usually come from a local content provider or // remote server. //初始化数据 initDataset(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.recycler_view_frag, container, false); rootView.setTag(TAG); // BEGIN_INCLUDE(initializeRecyclerView) mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerView); // LinearLayoutManager is used here, this will layout the elements in a similar fashion // to the way ListView would layout elements. The RecyclerView.LayoutManager defines how // elements are laid out. //使用LinearLayoutManager来初始化RecyclerView.LayoutManager,RecyclerView.LayoutManager决定recyclerView如何布局 mLayoutManager = new LinearLayoutManager(getActivity()); mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; if (savedInstanceState != null) { // Restore saved layout manager type. mCurrentLayoutManagerType = (LayoutManagerType) savedInstanceState .getSerializable(KEY_LAYOUT_MANAGER); } setRecyclerViewLayoutManager(mCurrentLayoutManagerType); mAdapter = new CustomAdapter(mDataset); // .RecyclerView设置适配器 mRecyclerView.setAdapter(mAdapter); // END_INCLUDE(initializeRecyclerView) //改变布局方式 mLinearLayoutRadioButton = (RadioButton) rootView.findViewById(R.id.linear_layout_rb); mLinearLayoutRadioButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setRecyclerViewLayoutManager(LayoutManagerType.LINEAR_LAYOUT_MANAGER); } }); mGridLayoutRadioButton = (RadioButton) rootView.findViewById(R.id.grid_layout_rb); mGridLayoutRadioButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setRecyclerViewLayoutManager(LayoutManagerType.GRID_LAYOUT_MANAGER); } }); return rootView; } /** * Set RecyclerView's LayoutManager to the one given. * 设置recyclerView的布局管理 * * @param layoutManagerType Type of layout manager to switch to. */ public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) { int scrollPosition = 0; // If a layout manager has already been set, get current scroll position. //如果当前的布局管理已近设置了,那么获得当前滑动的位置 if (mRecyclerView.getLayoutManager() != null) { scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()) .findFirstCompletelyVisibleItemPosition(); } //设置布局管理 switch (layoutManagerType) { case GRID_LAYOUT_MANAGER: mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT); mCurrentLayoutManagerType = LayoutManagerType.GRID_LAYOUT_MANAGER; break; case LINEAR_LAYOUT_MANAGER: mLayoutManager = new LinearLayoutManager(getActivity()); mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; break; default: mLayoutManager = new LinearLayoutManager(getActivity()); mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; } //滑动到上次的位置 mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.scrollToPosition(scrollPosition); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { // Save currently selected layout manager. //保存当前的布局管理对象 savedInstanceState.putSerializable(KEY_LAYOUT_MANAGER, mCurrentLayoutManagerType); super.onSaveInstanceState(savedInstanceState); } /** * Generates Strings for RecyclerView's adapter. This data would usually come * from a local content provider or remote server. * 生成数据 */ private void initDataset() { mDataset = new String[DATASET_COUNT]; for (int i = 0; i < DATASET_COUNT; i++) { mDataset[i] = "This is element #" + i; } } }
布局文件
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<RadioGroup android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="horizontal" android:checkedButton="@+id/linear_layout_rb">
<RadioButton android:id="@+id/linear_layout_rb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/linear_layout_manager"/>
<RadioButton android:id="@+id/grid_layout_rb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/grid_layout_manager"/>
</RadioGroup>
<android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent"/>
</LinearLayout>
适配器:
package com.example.android.recyclerview; import com.example.android.common.logger.Log; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; /** * Provide views to RecyclerView with data from mDataSet. * 自定义的RecyclerView数据适配器 */ public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> { private static final String TAG = "CustomAdapter"; private String[] mDataSet; // BEGIN_INCLUDE(recyclerViewSampleViewHolder) /** * Provide a reference to the type of views that you are using (custom ViewHolder) * 提供一个指向自定义View的应用 */ public static class ViewHolder extends RecyclerView.ViewHolder { /** * 这里不只可以是textView还可以是其他的View */ private final TextView textView; public ViewHolder(View v) { super(v); // Define click listener for the ViewHolder's View. //定义每一个View的点击事件 v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d(TAG, "Element " + getPosition() + " clicked."); } }); textView = (TextView) v.findViewById(R.id.textView); } public TextView getTextView() { return textView; } } // END_INCLUDE(recyclerViewSampleViewHolder) /** * Initialize the dataset of the Adapter. * * @param dataSet String[] containing the data to populate views to be used by RecyclerView. */ public CustomAdapter(String[] dataSet) { mDataSet = dataSet; } // BEGIN_INCLUDE(recyclerViewOnCreateViewHolder) // Create new views (invoked by the layout manager) @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { // Create a new view.创建一个View View v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.text_row_item, viewGroup, false); return new ViewHolder(v); } // END_INCLUDE(recyclerViewOnCreateViewHolder) // BEGIN_INCLUDE(recyclerViewOnBindViewHolder) // Replace the contents of a view (invoked by the layout manager) //由layout manager调用,替换ViewHolder的内容.在滑动的时候会不断调用这个方法来跟新视图 @Override public void onBindViewHolder(ViewHolder viewHolder, final int position) { Log.d(TAG, "Element " + position + " set."); // Get element from your dataset at this position and replace the contents of the view // with that element //这里得到的是ViewHolder创建时传递进去的可以自定义的View viewHolder.getTextView().setText(mDataSet[position]); } // END_INCLUDE(recyclerViewOnBindViewHolder) // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { return mDataSet.length; } }
说明:为什么要使用RecyclerView
从效果上说,RecyclerView和ListView是差不多的,都是列表。
①但是RecyclerViwe支持多种布局方式,可以通过设置不同的布局方式实现不同效果,这一点比较灵活。
②直接省去了当初的convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
因为RecyclerView帮我们封装了Holder,所以我们自己写的ViewHolder就需要继承RecyclerView.ViewHolder,只有这样,RecyclerView才能帮你去管理这个ViewHolder类。
③以前的getView方法的渲染数据部分的代码相当于onBindViewHolder(),所以如果调用adapter.notifyDataSetChanged()方法,应该也会重新调用onBindViewHolder()方法才对吧?实验后,果然如此!
除了adapter.notifyDataSetChanged()这个方法之外,新的Adapter还提供了其他的方法,如下:
public final void notifyDataSetChanged()
public final void notifyItemChanged(int position)
public final void notifyItemRangeChanged(int positionStart, int itemCount)
public final void notifyItemInserted(int position)
public final void notifyItemMoved(int fromPosition, int toPosition)
public final void notifyItemRangeInserted(int positionStart, int itemCount)
public final void notifyItemRemoved(int position)
public final void notifyItemRangeRemoved(int positionStart, int itemCount)
第一个方法没什么好讲的,跟以前一样。
notifyItemChanged(int position),position数据发生了改变,那调用这个方法,就会回调对应position的onBindViewHolder()方法了,当然,因为ViewHolder是复用的,所以如果position在当前屏幕以外,也就不会回调了,因为没有意义,下次position滚动会当前屏幕以内的时候同样会调用onBindViewHolder()方法刷新数据了。其他的方法也是同样的道理。
public final void notifyItemRangeChanged(int positionStart, int itemCount),顾名思义,可以刷新从positionStart开始itemCount数量的item了(这里的刷新指回调onBindViewHolder()方法)。
public final void notifyItemInserted(int position),这个方法是在第position位置被插入了一条数据的时候可以使用这个方法刷新,注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义。
public final void notifyItemMoved(int fromPosition, int toPosition),这个方法是从fromPosition移动到toPosition为止的时候可以使用这个方法刷新
public final void notifyItemRangeInserted(int positionStart, int itemCount),显然是批量添加。
public final void notifyItemRemoved(int position),第position个被删除的时候刷新,同样会有动画。
public final void notifyItemRangeRemoved(int positionStart, int itemCount),批量删除。
源码下载地址
package com.example.android.activityscenetransitionbasic;
import com.squareup.picasso.Picasso;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
/** * Our main Activity in this sample. Displays a grid of items which an image and title. When the * user clicks on an item, {@link DetailActivity} is launched, using the Activity Scene Transitions * framework to animatedly do so. * <p> * 当用户点击Gridview的时候,会使用Activity Scene Transitions framework(activity 场景切换框架)动画启动DetailActivity。 */
public class MainActivity extends Activity implements AdapterView.OnItemClickListener {
private GridView mGridView;
private GridAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.grid);
// Setup the GridView and set the adapter
mGridView = (GridView) findViewById(R.id.grid);
mGridView.setOnItemClickListener(this);
mAdapter = new GridAdapter();
mGridView.setAdapter(mAdapter);
}
/** * Called when an item in the {@link android.widget.GridView} is clicked. Here will launch the * {@link DetailActivity}, using the Scene Transition animation functionality. */
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Item item = (Item) adapterView.getItemAtPosition(position);
// Construct an Intent as normal常见一个普通的Intent
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra(DetailActivity.EXTRA_PARAM_ID, item.getId());
// BEGIN_INCLUDE(start_activity)
/** * Now create an {@link android.app.ActivityOptions} instance using the * {@link ActivityOptionsCompat#makeSceneTransitionAnimation(Activity, Pair[])} factory * method. * * 使用ActivityOptionsCompat#makeSceneTransitionAnimation(Activity, Pair[])工厂方法来创建一个ActivityOptions * 实例 */
ActivityOptionsCompat activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(
this,
// Now we provide a list of Pair items which contain the view we can transitioning
// from, and the name of the view it is transitioning to, in the launched activity
new Pair<View, String>(view.findViewById(R.id.imageview_item),
DetailActivity.VIEW_NAME_HEADER_IMAGE),
new Pair<View, String>(view.findViewById(R.id.textview_name),
DetailActivity.VIEW_NAME_HEADER_TITLE));
// Now we can start the Activity, providing the activity options as a bundle
ActivityCompat.startActivity(this, intent, activityOptions.toBundle());
// END_INCLUDE(start_activity)
}
/** * {@link android.widget.BaseAdapter} which displays items. */
private class GridAdapter extends BaseAdapter {
@Override
public int getCount() {
return Item.ITEMS.length;
}
@Override
public Item getItem(int position) {
return Item.ITEMS[position];
}
@Override
public long getItemId(int position) {
return getItem(position).getId();
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
if (view == null) {
view = getLayoutInflater().inflate(R.layout.grid_item, viewGroup, false);
}
final Item item = getItem(position);
// Load the thumbnail image加载缩略图
ImageView image = (ImageView) view.findViewById(R.id.imageview_item);
Picasso.with(image.getContext()).load(item.getThumbnailUrl()).into(image);
// Set the TextView's contents
TextView name = (TextView) view.findViewById(R.id.textview_name);
name.setText(item.getName());
return view;
}
}
}
数据:
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package com.example.android.activityscenetransitionbasic;
/** * Represents an Item in our application. Each item has a name, id, full size image url and * thumbnail url. */
public class Item {
//大图网址
private static final String LARGE_BASE_URL = "http://storage.googleapis.com/androiddevelopers/sample_data/activity_transition/large/";
//缩略图网址
private static final String THUMB_BASE_URL = "http://storage.googleapis.com/androiddevelopers/sample_data/activity_transition/thumbs/";
public static Item[] ITEMS = new Item[]{
new Item("Flying in the Light", "Romain Guy", "flying_in_the_light.jpg"),
new Item("Caterpillar", "Romain Guy", "caterpillar.jpg"),
new Item("Look Me in the Eye", "Romain Guy", "look_me_in_the_eye.jpg"),
new Item("Flamingo", "Romain Guy", "flamingo.jpg"),
new Item("Rainbow", "Romain Guy", "rainbow.jpg"),
new Item("Over there", "Romain Guy", "over_there.jpg"),
new Item("Jelly Fish 2", "Romain Guy", "jelly_fish_2.jpg"),
new Item("Lone Pine Sunset", "Romain Guy", "lone_pine_sunset.jpg"),
};
public static Item getItem(int id) {
for (Item item : ITEMS) {
if (item.getId() == id) {
return item;
}
}
return null;
}
private final String mName;
private final String mAuthor;
private final String mFileName;
Item(String name, String author, String fileName) {
mName = name;
mAuthor = author;
mFileName = fileName;
}
public int getId() {
return mName.hashCode() + mFileName.hashCode();
}
public String getAuthor() {
return mAuthor;
}
public String getName() {
return mName;
}
public String getPhotoUrl() {
return LARGE_BASE_URL + mFileName;
}
public String getThumbnailUrl() {
return THUMB_BASE_URL + mFileName;
}
}
跳转过来的第二个Activity
package com.example.android.activityscenetransitionbasic; import com.squareup.picasso.Picasso; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.support.v4.view.ViewCompat; import android.transition.Transition; import android.widget.ImageView; import android.widget.TextView; /** * Our secondary Activity which is launched from {@link MainActivity}. Has a simple detail UI * which has a large banner image, title and body text. */ public class DetailActivity extends Activity { // Extra name for the ID parameter public static final String EXTRA_PARAM_ID = "detail:_id"; // View name of the header image. Used for activity scene transitions public static final String VIEW_NAME_HEADER_IMAGE = "detail:header:image"; // View name of the header title. Used for activity scene transitions public static final String VIEW_NAME_HEADER_TITLE = "detail:header:title"; private ImageView mHeaderImageView; private TextView mHeaderTitle; private Item mItem; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.details); // Retrieve the correct Item instance, using the ID provided in the Intent //接收上一个Activity中传递过来的值 mItem = Item.getItem(getIntent().getIntExtra(EXTRA_PARAM_ID, 0)); mHeaderImageView = (ImageView) findViewById(R.id.imageview_header); mHeaderTitle = (TextView) findViewById(R.id.textview_title); // BEGIN_INCLUDE(detail_set_view_name) /** * Set the name of the view's which will be transition to, using the static values above. * This could be done in the layout XML, but exposing it via static variables allows easy * querying from other Activities * 使用上边定义的常亮设置前一个Activity中的View对应当前Activity中的那个View *因为场景切换其实是这俩个Activity中的场景有连贯性,所以在2个activity切换过程中向做到View也看起来是连贯的 * 切换过来的效果,所以要做这样的对应关系。 */ ViewCompat.setTransitionName(mHeaderImageView, VIEW_NAME_HEADER_IMAGE); ViewCompat.setTransitionName(mHeaderTitle, VIEW_NAME_HEADER_TITLE); // END_INCLUDE(detail_set_view_name) loadItem(); } private void loadItem() { // Set the title TextView to the item's name and author mHeaderTitle.setText(getString(R.string.image_header, mItem.getName(), mItem.getAuthor())); //如果当前系统是5.0以上的系统并添加了场景切换效果的监听事件 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && addTransitionListener()) { // If we're running on Lollipop and we have added a listener to the shared element // transition, load the thumbnail. The listener will load the full-size image when // the transition is complete. loadThumbnail(); } else { // If all other cases we should just load the full-size image now loadFullSizeImage(); } } /** * Load the item's thumbnail image into our {@link ImageView}. */ private void loadThumbnail() { Picasso.with(mHeaderImageView.getContext()) .load(mItem.getThumbnailUrl()) .noFade() .into(mHeaderImageView); } /** * Load the item's full-size image into our {@link ImageView}. */ private void loadFullSizeImage() { Picasso.with(mHeaderImageView.getContext()) .load(mItem.getPhotoUrl()) .noFade() .noPlaceholder() .into(mHeaderImageView); } /** * Try and add a {@link Transition.TransitionListener} to the entering shared element * {@link Transition}. We do this so that we can load the full-size image after the transition * has completed. * * @return true if we were successful in adding a listener to the enter transition */ private boolean addTransitionListener() { //PAI 21 final Transition transition = getWindow().getSharedElementEnterTransition(); if (transition != null) { // There is an entering shared element transition so add a listener to it transition.addListener(new Transition.TransitionListener() { @Override public void onTransitionEnd(Transition transition) { // As the transition has ended, we can now load the full-size image loadFullSizeImage(); // Make sure we remove ourselves as a listener transition.removeListener(this); } @Override public void onTransitionStart(Transition transition) { // No-op } @Override public void onTransitionCancel(Transition transition) { // Make sure we remove ourselves as a listener transition.removeListener(this); } @Override public void onTransitionPause(Transition transition) { // No-op } @Override public void onTransitionResume(Transition transition) { // No-op } }); return true; } // If we reach here then we have not added a listener return false; } }
git地址
注意:需要打开Settings > Accessibility > TalkBack功能。当点击这按钮的时候会有提示语音。
下面是一个自定义的View,添加到XML中使用就可以。
package com.example.android.basicaccessibility;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
/** * 自定义控件的访问性(即与用户的交互) * Custom view to demonstrate accessibility. * <p> * 这个控件没有使用任何框架层的控件,因此不能自动的与用户进行交互。我们使用android.view.accessibility.AccessibilityEvent来给控件提供与系统交互的能力 * <p>This view does not use any framework widgets, so does not get any accessibility features * automatically. Instead, we use {@link android.view.accessibility.AccessibilityEvent} to provide accessibility hints to * the OS. * 例如,如果把TalkBack设置成可以使用的,那么当用户讲话的时候就会收到这个Vie的反馈 * <p> * <p>For example, if TalkBack is enabled, users will be able to receive spoken feedback as they * interact with this view. * 更一般的,这个View有多个位置分别指向1到4.当每次拨号的时候,下一个位置将会被选中 * <p> * <p>More generally, this view renders a multi-position "dial" that can be used to select a value * between 1 and 4. Each time the dial is clicked, the next position will be selected (modulo * the maximum number of positions). */
public class DialView extends View {
private static int SELECTION_COUNT = 4;
private static float FONT_SIZE = 40f;
private float mWidth;
private float mHeight;
private float mWidthPadded;
private float mHeightPadded;
private Paint mTextPaint;
private Paint mDialPaint;
private float mRadius;
private int mActiveSelection;
/** * 构造函数将会在加载XMl文件时被调用。 * Constructor that is called when inflating a view from XML. This is called * when a view is being constructed from an XML file, supplying attributes * that were specified in the XML file. * <p> * <p>In our case, this constructor just calls init(). * 当前View所正在运行的上下文 * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. * */
public DialView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/** * 初始化本地变量。 * Helper method to initialize instance variables. Called by constructor. */
private void init() {
//画笔在这里创建。
// Paint styles used for rendering are created here, rather than at render-time. This
// is a performance optimization, since onDraw() will get called frequently.
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(Color.BLACK);
mTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(FONT_SIZE);
mDialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDialPaint.setColor(Color.GRAY);
// Initialize current selection. This will store where the dial's "indicator" is pointing.
mActiveSelection = 0;
// Setup onClick listener for this view. Rotates between each of the different selection
// states on each click.
//
// Notice that we call sendAccessibilityEvent here. Some AccessibilityEvents are generated
// by the system. However, custom views will typically need to send events manually as the
// user interacts with the view. The type of event sent will vary, depending on the nature
// of the view and how the user interacts with it.
//
// In this case, we are sending TYPE_VIEW_SELECTED rather than TYPE_VIEW_CLICKED, because
// clicking on this view selects a new value.
//
// We will give our AccessibilityEvent further information about the state of the view in
// onPopulateAccessibilityEvent(), which will be called automatically by the system
// for each AccessibilityEvent.
/** * 当点击的时候调用SendAccessibilityEvent来生成一个AccessibilityEvents发送给系统。然而AccessibilityEvents * 也是有类型的,具体选用什么类型取决于用户怎么个View交互。在这个例子中当用户点击View的时候View会改变选择的状态 * 因此,类型选择TYPE_VIEW_SELECTED而不是TYPE_VIEW_CLICKED * * * 我们可以给AccessibilityEvent在onPopulateAccessibilityEvent()方法中添加更多的一些关于View状态的信息。 * 因为系统会为每一个AccessibilityEvent调用onPopulateAccessibilityEvent()方法 * * * 关于事件的接收:这里并没有对发出的事件进行处理。要想对事件处理必须实现AccessibilityService。例子参看 * http://krelve.com/?post=26 * *这个功能主要实现的是android里边的辅助功能。对于盲人点击屏幕的时候可能不知道点哪了。但是通过这种方式可以 * 方便的进行语音提示或者震动 * * */
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Rotate selection to the next valid choice.
mActiveSelection = (mActiveSelection + 1) % SELECTION_COUNT;
// Send an AccessibilityEvent, since the user has interacted with the view.
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
// Redraw the entire view. (Inefficient, but this is sufficient for demonstration
// purposes.)
invalidate();
}
});
}
/** * This is where a View should populate outgoing accessibility events with its text content. * While this method is free to modify event attributes other than text content, doing so * should normally be performed in * {@link #onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent)}. * <p> * <p>Note that the behavior of this method will typically vary, depending on the type of * accessibility event is passed into it. The allowed values also very, and are documented * in {@link android.view.accessibility.AccessibilityEvent}. * <p> * <p>Typically, this is where you'll describe the state of your custom view. You may also * want to provide custom directions when the user has focused your view. * * @param event The accessibility event which to populate. */
// BEGIN_INCLUDE (on_populate_accessibility_event)
@Override
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
// Detect what type of accessibility event is being passed in.
//得到当前事件的类型
int eventType = event.getEventType();
// Common case: The user has interacted with our view in some way. State may or may not
// have been changed. Read out the current status of the view.
//
// We also set some other metadata which is not used by TalkBack, but could be used by
// other TTS engines.
/** * 通常情况下当方法执行到这里时表示用户已近和View发生了交互。但是传递过来的事件状态可能是改变的也可能是没有改变的 * * * 我们在这里添加一些其他的元数据但是没有收到反馈。但是这些数据可能被语音引擎使用 * * 需要到设置里边打开服务才能听到声音。OK? * To run this sample as intended you will need to turn on TalkBack (or another text to speach system) on your Android device. Go to Settings > Accessibility > TalkBack to turn TalkBack on. When you run the sample with TalkBack mode on, TalkBack will read aloud the description of the in-focus widget. */
if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED ||
eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
//给AccessibilityEvent添加信息
event.getText().add("Mode selected: " + Integer.toString(mActiveSelection + 1) + ".");
event.setItemCount(SELECTION_COUNT);
event.setCurrentItemIndex(mActiveSelection);
}
// When a user first focuses on our view, we'll also read out some simple instructions to
// make it clear that this is an interactive element.
if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
event.getText().add("Tap to change.");
}
}
// END_INCLUDE (on_populate_accessibility_event)
/** * 当View大小改变的时候调用。当View第一次被添加到一个视图结构中去的时候,掺入的参数oldw oldh为0 * This is called during layout when the size of this view has changed. If * you were just added to the view hierarchy, you're called with the old * values of 0. * <p> * 在这个方法里我们可以确定我们View的大小 * <p>This is where we determine the drawing bounds for our custom view. * * @param w Current width of this view. * @param h Current height of this view. * @param oldw Old width of this view. * @param oldh Old height of this view. */
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// Account for padding
float xPadding = (float) (getPaddingLeft() + getPaddingRight());
float yPadding = (float) (getPaddingTop() + getPaddingBottom());
// Compute available width/height
mWidth = w;
mHeight = h;
mWidthPadded = w - xPadding;
mHeightPadded = h - yPadding;
mRadius = (float) (Math.min(mWidth, mHeight) / 2 * 0.8);
}
/** * 绘制内容 * Render view content. * <p> * 绘制一个灰色的外圆当做是拨号盘,再画一个小的圆当做是表盘刻度。表盘刻度额位置由mActiveSelection变量决定 * <p>We render an outer grey circle to serve as our "dial", and then render a smaller black * circle to server as our indicator. The position for the indicator is determined based * on mActiveSelection. * * @param canvas the canvas on which the background will be drawn */
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw dial
canvas.drawCircle(mWidth / 2, mHeight / 2, (float) mRadius, mDialPaint);
// Draw text labels
final float labelRadius = mRadius + 10;
for (int i = 0; i < SELECTION_COUNT; i++) {
float[] xyData = computeXYForPosition(i, labelRadius);
float x = xyData[0];
float y = xyData[1];
canvas.drawText(Integer.toString(i + 1), x, y, mTextPaint);
}
// Draw indicator mark
final float markerRadius = mRadius - 35;
float[] xyData = computeXYForPosition(mActiveSelection, markerRadius);
float x = xyData[0];
float y = xyData[1];
canvas.drawCircle(x, y, 20, mTextPaint);
}
/** * Compute the X/Y-coordinates for a label or indicator, given the position number and radius * where the label should be drawn. * * @param pos Zero based position index * @param radius Radius where label/indicator is to be drawn. * @return 2-element array. Element 0 is X-coordinate, element 1 is Y-coordinate. */
private float[] computeXYForPosition(final int pos, final float radius) {
float[] result = new float[2];
Double startAngle = Math.PI * (9 / 8d); // Angles are in radiansq
Double angle = startAngle + (pos * (Math.PI / 4));
result[0] = (float) (radius * Math.cos(angle)) + (mWidth / 2);
result[1] = (float) (radius * Math.sin(angle)) + (mHeight / 2);
return result;
}
}
Git地址