原文地址:https://developer.android.com/training/custom-views/index.html

Creating Custom Views

Get started

Dependencies and prerequisites

  • Android 2.1 (API level 7) or higher

You should also read

  • Custom Components
  • Input Events
  • Property Animation
  • Hardware Acceleration
  • Accessibility developer guide

Try it out

DOWNLOAD THE SAMPLE

CustomView.zip

The Android framework has a large set of View classes for interacting with the user and displaying various types of data. But sometimes your app has unique needs that aren’t covered by the built-in views. This class shows you how to create your own views that are robust and reusable.

Lessons


Creating a View Class
Create a class that acts like a built-in view, with custom attributes and support from the  Android Studio layout editor.
Custom Drawing
Make your view visually distinctive using the Android graphics system.
Making the View Interactive
Users expect a view to react smoothly and naturally to input gestures. This lesson discusses how to use gesture detection, physics, and animation to give your user interface a professional feel.
Optimizing the View
No matter how beautiful your UI is, users won't love it if it doesn't run at a consistently high frame rate. Learn how to avoid common performance problems, and how to use hardware acceleration to make your custom drawings run faster.

一、Creating a View Class

Previous Next

This lesson teaches you to

  1. Subclass a View
  2. Define Custom Attributes
  3. Apply Custom Attributes to a View
  4. Add Properties and Events
  5. Design For Accessibility

You should also read

  • Custom Components

Try it out

DOWNLOAD THE SAMPLE

CustomView.zip

A well-designed custom view is much like any other well-designed class. It encapsulates a specific set of functionality with an easy to use interface, it uses CPU and memory efficiently, and so forth. In addition to being a well-designed class, though, a custom view should:

  • Conform to Android standards
  • Provide custom styleable attributes that work with Android XML layouts
  • Send accessibility events
  • Be compatible with multiple Android platforms.

The Android framework provides a set of base classes and XML tags to help you create a view that meets all of these requirements. This lesson discusses how to use the Android framework to create the core functionality of a view class.

Subclass a View


All of the view classes defined in the Android framework extend View. Your custom view can also extend View directly, or you can save time by extending one of the existing view subclasses, such asButton.

To allow Android Studio to interact with your view, at a minimum you must provide a constructor that takes a Context and an AttributeSet object as parameters. This constructor allows the layout editor to create and edit an instance of your view.

class PieChart extends View {    public PieChart(Context context, AttributeSet attrs) {        super(context, attrs);    }}

Define Custom Attributes


To add a built-in View to your user interface, you specify it in an XML element and control its appearance and behavior with element attributes. Well-written custom views can also be added and styled via XML. To enable this behavior in your custom view, you must:

  • Define custom attributes for your view in a  resource element
  • Specify values for the attributes in your XML layout
  • Retrieve attribute values at runtime
  • Apply the retrieved attribute values to your view

This section discusses how to define custom attributes and specify their values. The next section deals with retrieving and applying the values at runtime.

To define custom attributes, add  resources to your project. It's customary to put these resources into ares/values/attrs.xml file. Here's an example of an attrs.xml file:

    name="PieChart">        name="showText" format="boolean" />        name="labelPosition" format="enum">            name="left" value="0"/>            name="right" value="1"/>          

This code declares two custom attributes, showText and labelPosition, that belong to a styleable entity named PieChart. The name of the styleable entity is, by convention, the same name as the name of the class that defines the custom view. Although it's not strictly necessary to follow this convention, many popular code editors depend on this naming convention to provide statement completion.

Once you define the custom attributes, you can use them in layout XML files just like built-in attributes. The only difference is that your custom attributes belong to a different namespace. Instead of belonging to the http://schemas.android.com/apk/res/android namespace, they belong to http://schemas.android.com/apk/res/[your package name]. For example, here's how to use the attributes defined for PieChart:

<?xml version="1.0" encoding="utf-8"?> xmlns:android="http://schemas.android.com/apk/res/android"   xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">      custom:showText="true"     custom:labelPosition="left" />

In order to avoid having to repeat the long namespace URI, the sample uses an xmlns directive. This directive assigns the alias custom to the namespace http://schemas.android.com/apk/res/com.example.customviews. You can choose any alias you want for your namespace.

Notice the name of the XML tag that adds the custom view to the layout. It is the fully qualified name of the custom view class. If your view class is an inner class, you must further qualify it with the name of the view's outer class. further. For instance, the PieChart class has an inner class calledPieView. To use the custom attributes from this class, you would use the tag com.example.customviews.charting.PieChart$PieView.

Apply Custom Attributes


When a view is created from an XML layout, all of the attributes in the XML tag are read from the resource bundle and passed into the view's constructor as an AttributeSet. Although it's possible to read values from the AttributeSet directly, doing so has some disadvantages:

  • Resource references within attribute values are not resolved
  • Styles are not applied

Instead, pass the AttributeSet to obtainStyledAttributes(). This method passes back a TypedArray array of values that have already been dereferenced and styled.

The Android resource compiler does a lot of work for you to make calling obtainStyledAttributes() easier. For each resource in the res directory, the generated R.java defines both an array of attribute ids and a set of constants that define the index for each attribute in the array. You use the predefined constants to read the attributes from the TypedArray. Here's how the PieChart class reads its attributes:

public PieChart(Context context, AttributeSet attrs) {   super(context, attrs);   TypedArray a = context.getTheme().obtainStyledAttributes(        attrs,        R.styleable.PieChart,        0, 0);   try {       mShowText = a.getBoolean(R.styleable.PieChart_showText, false);       mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);   } finally {       a.recycle();   }}

Note that TypedArray objects are a shared resource and must be recycled after use.

Add Properties and Events


Attributes are a powerful way of controlling the behavior and appearance of views, but they can only be read when the view is initialized. To provide dynamic behavior, expose a property getter and setter pair for each custom attribute. The following snippet shows how PieChart exposes a property called showText:

public boolean isShowText() {   return mShowText;}public void setShowText(boolean showText) {   mShowText = showText;   invalidate();   requestLayout();}

Notice that setShowText calls invalidate() and requestLayout(). These calls are crucial to ensure that the view behaves reliably. You have to invalidate the view after any change to its properties that might change its appearance, so that the system knows that it needs to be redrawn. Likewise, you need to request a new layout if a property changes that might affect the size or shape of the view. Forgetting these method calls can cause hard-to-find bugs.

Custom views should also support event listeners to communicate important events. For instance, PieChart exposes a custom event calledOnCurrentItemChanged to notify listeners that the user has rotated the pie chart to focus on a new pie slice.

It's easy to forget to expose properties and events, especially when you're the only user of the custom view. Taking some time to carefully define your view's interface reduces future maintenance costs. A good rule to follow is to always expose any property that affects the visible appearance or behavior of your custom view.

Design For Accessibility


Your custom view should support the widest range of users. This includes users with disabilities that prevent them from seeing or using a touchscreen. To support users with disabilities, you should:

  • Label your input fields using the android:contentDescription attribute
  • Send accessibility events by calling sendAccessibilityEvent() when appropriate.
  • Support alternate controllers, such as D-pad and trackball

For more information on creating accessible views, see Making Applications Accessible in the Android Developers Guide.


二、Custom Drawing

Previous Next

This lesson teaches you to

  1. Override onDraw()
  2. Create Drawing Objects
  3. Handle Layout Events
  4. Draw!

You should also read

  • Canvas and Drawables

Try it out

DOWNLOAD THE SAMPLE

CustomView.zip

The most important part of a custom view is its appearance. Custom drawing can be easy or complex according to your application's needs. This lesson covers some of the most common operations.

Override onDraw()


The most important step in drawing a custom view is to override the onDraw() method. The parameter to onDraw() is a Canvas object that the view can use to draw itself. The Canvas class defines methods for drawing text, lines, bitmaps, and many other graphics primitives. You can use these methods in onDraw() to create your custom user interface (UI).

Before you can call any drawing methods, though, it's necessary to create a Paint object. The next section discusses Paint in more detail.

Create Drawing Objects


The android.graphics framework divides drawing into two areas:

  • What to draw, handled by Canvas
  • How to draw, handled by Paint.

For instance, Canvas provides a method to draw a line, while Paint provides methods to define that line's color. Canvas has a method to draw a rectangle, while Paint defines whether to fill that rectangle with a color or leave it empty. Simply put, Canvas defines shapes that you can draw on the screen, while Paint defines the color, style, font, and so forth of each shape you draw.

So, before you draw anything, you need to create one or more Paint objects. The PieChart example does this in a method called init, which is called from the constructor:

private void init() {   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);   mTextPaint.setColor(mTextColor);   if (mTextHeight == 0) {       mTextHeight = mTextPaint.getTextSize();   } else {       mTextPaint.setTextSize(mTextHeight);   }   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);   mPiePaint.setStyle(Paint.Style.FILL);   mPiePaint.setTextSize(mTextHeight);   mShadowPaint = new Paint(0);   mShadowPaint.setColor(0xff101010);   mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));   ...

Creating objects ahead of time is an important optimization. Views are redrawn very frequently, and many drawing objects require expensive initialization. Creating drawing objects within your onDraw() method significantly reduces performance and can make your UI appear sluggish.

Handle Layout Events


In order to properly draw your custom view, you need to know what size it is. Complex custom views often need to perform multiple layout calculations depending on the size and shape of their area on screen. You should never make assumptions about the size of your view on the screen. Even if only one app uses your view, that app needs to handle different screen sizes, multiple screen densities, and various aspect ratios in both portrait and landscape mode.

Although View has many methods for handling measurement, most of them do not need to be overridden. If your view doesn't need special control over its size, you only need to override one method: onSizeChanged().

onSizeChanged() is called when your view is first assigned a size, and again if the size of your view changes for any reason. Calculate positions, dimensions, and any other values related to your view's size in onSizeChanged(), instead of recalculating them every time you draw. In the PieChartexample, onSizeChanged() is where the PieChart view calculates the bounding rectangle of the pie chart and the relative position of the text label and other visual elements.

When your view is assigned a size, the layout manager assumes that the size includes all of the view's padding. You must handle the padding values when you calculate your view's size. Here's a snippet from PieChart.onSizeChanged() that shows how to do this:

       // Account for padding       float xpad = (float)(getPaddingLeft() + getPaddingRight());       float ypad = (float)(getPaddingTop() + getPaddingBottom());       // Account for the label       if (mShowText) xpad += mTextWidth;       float ww = (float)w - xpad;       float hh = (float)h - ypad;       // Figure out how big we can make the pie.       float diameter = Math.min(ww, hh);

If you need finer control over your view's layout parameters, implement onMeasure(). This method's parameters are View.MeasureSpec values that tell you how big your view's parent wants your view to be, and whether that size is a hard maximum or just a suggestion. As an optimization, these values are stored as packed integers, and you use the static methods of View.MeasureSpec to unpack the information stored in each integer.

Here's an example implementation of onMeasure(). In this implementation, PieChart attempts to make its area big enough to make the pie as big as its label:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   // Try for a width based on our minimum   int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();   int w = resolveSizeAndState(minw, widthMeasureSpec, 1);   // Whatever the width ends up being, ask for a height that would let the pie   // get as big as it can   int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();   int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);   setMeasuredDimension(w, h);}

There are three important things to note in this code:

  • The calculations take into account the view's padding. As mentioned earlier, this is the view's responsibility.
  • The helper method resolveSizeAndState() is used to create the final width and height values. This helper returns an appropriateView.MeasureSpec value by comparing the view's desired size to the spec passed into onMeasure().
  • onMeasure() has no return value. Instead, the method communicates its results by calling setMeasuredDimension(). Calling this method is mandatory. If you omit this call, the View class throws a runtime exception.

Draw!


Once you have your object creation and measuring code defined, you can implement onDraw(). Every view implements onDraw() differently, but there are some common operations that most views share:

  • Draw text using drawText(). Specify the typeface by calling setTypeface(), and the text color by calling setColor().
  • Draw primitive shapes using drawRect()drawOval(), and drawArc(). Change whether the shapes are filled, outlined, or both by callingsetStyle().
  • Draw more complex shapes using the Path class. Define a shape by adding lines and curves to a Path object, then draw the shape usingdrawPath(). Just as with primitive shapes, paths can be outlined, filled, or both, depending on the setStyle().
  • Define gradient fills by creating LinearGradient objects. Call setShader() to use your LinearGradient on filled shapes.
  • Draw bitmaps using drawBitmap().

For example, here's the code that draws PieChart. It uses a mix of text, lines, and shapes.

protected void onDraw(Canvas canvas) {   super.onDraw(canvas);   // Draw the shadow   canvas.drawOval(           mShadowBounds,           mShadowPaint   );   // Draw the label text   canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);   // Draw the pie slices   for (int i = 0; i < mData.size(); ++i) {       Item it = mData.get(i);       mPiePaint.setShader(it.mShader);       canvas.drawArc(mBounds,               360 - it.mEndAngle,               it.mEndAngle - it.mStartAngle,               true, mPiePaint);   }   // Draw the pointer   canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);   canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);}

三、Making the View Interactive

This lesson teaches you to

  1. Handle Input Gestures
  2. Create Physically Plausible Motion
  3. Make Your Transitions Smooth

You should also read

  • Input Events
  • Property Animation

Try it out

DOWNLOAD THE SAMPLE

CustomView.zip

Drawing a UI is only one part of creating a custom view. You also need to make your view respond to user input in a way that closely resembles the real-world action you're mimicking. Objects should always act in the same way that real objects do. For example, images should not immediately pop out of existence and reappear somewhere else, because objects in the real world don't do that. Instead, images should move from one place to another.

Users also sense subtle behavior or feel in an interface, and react best to subtleties that mimic the real world. For example, when users fling a UI object, they should sense friction at the beginning that delays the motion, and then at the end sense momentum that carries the motion beyond the fling.

This lesson demonstrates how to use features of the Android framework to add these real-world behaviors to your custom view.

Handle Input Gestures


Like many other UI frameworks, Android supports an input event model. User actions are turned into events that trigger callbacks, and you can override the callbacks to customize how your application responds to the user. The most common input event in the Android system is touch, which triggersonTouchEvent(android.view.MotionEvent). Override this method to handle the event:

   @Override   public boolean onTouchEvent(MotionEvent event) {    return super.onTouchEvent(event);   }

Touch events by themselves are not particularly useful. Modern touch UIs define interactions in terms of gestures such as tapping, pulling, pushing, flinging, and zooming. To convert raw touch events into gestures, Android provides GestureDetector.

Construct a GestureDetector by passing in an instance of a class that implements GestureDetector.OnGestureListener. If you only want to process a few gestures, you can extend GestureDetector.SimpleOnGestureListener instead of implementing theGestureDetector.OnGestureListener interface. For instance, this code creates a class that extendsGestureDetector.SimpleOnGestureListener and overrides onDown(MotionEvent).

class mListener extends GestureDetector.SimpleOnGestureListener {   @Override   public boolean onDown(MotionEvent e) {       return true;   }}mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());

Whether or not you use GestureDetector.SimpleOnGestureListener, you must always implement an onDown() method that returns true. This step is necessary because all gestures begin with an onDown() message. If you return false from onDown(), asGestureDetector.SimpleOnGestureListener does, the system assumes that you want to ignore the rest of the gesture, and the other methods ofGestureDetector.OnGestureListener never get called. The only time you should return false from onDown() is if you truly want to ignore an entire gesture. Once you've implemented GestureDetector.OnGestureListener and created an instance of GestureDetector, you can use yourGestureDetector to interpret the touch events you receive in onTouchEvent().

@Overridepublic boolean onTouchEvent(MotionEvent event) {   boolean result = mDetector.onTouchEvent(event);   if (!result) {       if (event.getAction() == MotionEvent.ACTION_UP) {           stopScrolling();           result = true;       }   }   return result;}

When you pass onTouchEvent() a touch event that it doesn't recognize as part of a gesture, it returns false. You can then run your own custom gesture-detection code.

Create Physically Plausible Motion


Gestures are a powerful way to control touchscreen devices, but they can be counterintuitive and difficult to remember unless they produce physically plausible results. A good example of this is the fling gesture, where the user quickly moves a finger across the screen and then lifts it. This gesture makes sense if the UI responds by moving quickly in the direction of the fling, then slowing down, as if the user had pushed on a flywheel and set it spinning.

However, simulating the feel of a flywheel isn't trivial. A lot of physics and math are required to get a flywheel model working correctly. Fortunately, Android provides helper classes to simulate this and other behaviors. The Scroller class is the basis for handling flywheel-style fling gestures.

To start a fling, call fling() with the starting velocity and the minimum and maximum x and y values of the fling. For the velocity value, you can use the value computed for you by GestureDetector.

@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {   mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);   postInvalidate();}

Note: Although the velocity calculated by GestureDetector is physically accurate, many developers feel that using this value makes the fling animation too fast. It's common to divide the x and y velocity by a factor of 4 to 8.

The call to fling() sets up the physics model for the fling gesture. Afterwards, you need to update the Scroller by callingScroller.computeScrollOffset() at regular intervals. computeScrollOffset() updates the Scroller object's internal state by reading the current time and using the physics model to calculate the x and y position at that time. Call getCurrX() and getCurrY() to retrieve these values.

Most views pass the Scroller object's x and y position directly to scrollTo(). The PieChart example is a little different: it uses the current scroll y position to set the rotational angle of the chart.

if (!mScroller.isFinished()) {    mScroller.computeScrollOffset();    setPieRotation(mScroller.getCurrY());}

The Scroller class computes scroll positions for you, but it does not automatically apply those positions to your view. It's your responsibility to make sure you get and apply new coordinates often enough to make the scrolling animation look smooth. There are two ways to do this:

  • Call postInvalidate() after calling fling(), in order to force a redraw. This technique requires that you compute scroll offsets in onDraw() and call postInvalidate() every time the scroll offset changes.
  • Set up a ValueAnimator to animate for the duration of the fling, and add a listener to process animation updates by callingaddUpdateListener().

The PieChart example uses the second approach. This technique is slightly more complex to set up, but it works more closely with the animation system and doesn't require potentially unnecessary view invalidation. The drawback is that ValueAnimator is not available prior to API level 11, so this technique cannot be used on devices running Android versions lower than 3.0.

Note: You can use ValueAnimator in applications that target lower API levels. You just need to make sure to check the current API level at runtime, and omit the calls to the view animation system if the current level is less than 11.

       mScroller = new Scroller(getContext(), null, true);       mScrollAnimator = ValueAnimator.ofFloat(0,1);       mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {           @Override           public void onAnimationUpdate(ValueAnimator valueAnimator) {               if (!mScroller.isFinished()) {                   mScroller.computeScrollOffset();                   setPieRotation(mScroller.getCurrY());               } else {                   mScrollAnimator.cancel();                   onScrollFinished();               }           }       });

Make Your Transitions Smooth


Users expect a modern UI to transition smoothly between states. UI elements fade in and out instead of appearing and disappearing. Motions begin and end smoothly instead of starting and stopping abruptly. The Android property animation framework, introduced in Android 3.0, makes smooth transitions easy.

To use the animation system, whenever a property changes that will affect your view's appearance, do not change the property directly. Instead, useValueAnimator to make the change. In the following example, modifying the currently selected pie slice in PieChart causes the entire chart to rotate so that the selection pointer is centered in the selected slice. ValueAnimator changes the rotation over a period of several hundred milliseconds, rather than immediately setting the new rotation value.

mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);mAutoCenterAnimator.setIntValues(targetAngle);mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);mAutoCenterAnimator.start();

If the value you want to change is one of the base View properties, doing the animation is even easier, because Views have a built-inViewPropertyAnimator that is optimized for simultaneous animation of multiple properties. For example:

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();

四、Optimizing the View

Previous Next

This lesson teaches you to

  • Do Less, Less Frequently

Try it out

DOWNLOAD THE SAMPLE

CustomView.zip

Now that you have a well-designed view that responds to gestures and transitions between states, ensure that the view runs fast. To avoid a UI that feels sluggish or stutters during playback, ensure that animations consistently run at 60 frames per second.

Do Less, Less Frequently


To speed up your view, eliminate unnecessary code from routines that are called frequently. Start by working on onDraw(), which will give you the biggest payback. In particular you should eliminate allocations in onDraw(), because allocations may lead to a garbage collection that would cause a stutter. Allocate objects during initialization, or between animations. Never make an allocation while an animation is running.

In addition to making onDraw() leaner, also make sure it's called as infrequently as possible. Most calls to onDraw() are the result of a call to invalidate(), so eliminate unnecessary calls to invalidate().

Another very expensive operation is traversing layouts. Any time a view calls requestLayout(), the Android UI system needs to traverse the entire view hierarchy to find out how big each view needs to be. If it finds conflicting measurements, it may need to traverse the hierarchy multiple times. UI designers sometimes create deep hierarchies of nested ViewGroup objects in order to get the UI to behave properly. These deep view hierarchies cause performance problems. Make your view hierarchies as shallow as possible.

If you have a complex UI, consider writing a custom ViewGroup to perform its layout. Unlike the built-in views, your custom view can make application-specific assumptions about the size and shape of its children, and thus avoid traversing its children to calculate measurements. The PieChart example shows how to extend ViewGroup as part of a custom view. PieChart has child views, but it never measures them. Instead, it sets their sizes directly according to its own custom layout algorithm.


更多相关文章

  1. 修复android获取ip地址失败 Fix Obtaining IP Address Android W
  2. AndroidStudio 2.3.3 百度云盘地址
  3. android githubf地址
  4. ADT Bundle最后一次更新的版本下载地址
  5. Google Maps API Key申请方法及地址
  6. Android】获取Mac地址
  7. android 获取mac地址
  8. android 开发工具下载地址

随机推荐

  1. Android用AsyncTask来下载图片及用AsyncT
  2. android 空调遥控器——红外设备(基础)
  3. android回调机制总结
  4. Android(安卓)控件自动“移入、暂停、移
  5. 介绍一个很全面源码关于android 账户管理
  6. android处理拍照旋转问题及带来的对内存
  7. Android实训案例(九)——答题系统的思绪,自
  8. Android如何正确的保存文件
  9. Android高效率编码-第三方SDK详解系列(三
  10. 在android中使用Get方式提交数据