前言:
-
本文翻译于 sonymobile 的一系列教程,指导 Android 开发人员如何制作3D风格的ListView,共三部分。这是第一部分,原文在已经官网找不到了,但我之前保存了html文档,所以这里无法给出原文链接,我会一段一段地给出原文,并在下面给出翻译。
这是我第一次尝试做翻译,不当之处请多指教。
这里先贴一张最终的效果图。
image
正文:
原文:
Making your own 3D list – Part 1
This is the first tutorial in a series of three on how to make a cool looking list in an android application. My name is Anders Ericson and I have been working with user interface (UI), for example the one in the Timescape™ application that you can find in the Xperia™ X10 mini and Xperia X10™ mini pro. Since the UI is one of the first things users will notice when they try an application the first time I wanted to create a tutorial that allow any android developer to create their own custom list view, similar to the one in Timescape™, with 3D feel and dynamics.
译文:
制作你自己的 3D ListView--第一节
这是三节课程中的第一节,这一系列课程讲述在Android应用中怎样制作一个看起来酷炫的列表。我是Anders Ericson,我一直主攻用户交互,例如Timescape™应用中的一个。这个应用你可以在Xperia™ X10 mini and Xperia X10™ mini pro这两款手机中找到。当用户体验一个应用的时候,UI是他们最先注意到的,因此我首先想创建一个课程,这个课程能使任何android开发人员创建他们自定义的ListView,就想Timescape™应用中的一样,有3D感和动力感。
原文:
Since this is quite a lot of code to go through in one article, I have divided it up into three parts. The first part (this one) starts with creating a basic list. It’s quite a lot of material to cover, but I want to get it out of the way so we can focus more on the fun stuff later. In the second part we’ll look into changing the appearance of the list and doing some 3D-like graphics. In the final part we’ll change the behavior of the list and add some “physics” to it, something that will improve the the look and feel of our list a lot!
译文:
由于完成整篇文章会有很多的代码,我已经将它划分成3个部分,第一部分(这部分)以创建一个基本的列表开始,确实需要涉及很多材料,但我想先解决它(译注:实现基本列表),从而我们可以在后面的关注更多有趣的事情。在第二部分我们将研究改变列表的外观,做一些3D效果的绘制。在最后的部分,我们将改变列表的行为,给它添加一些物理现象,这些物理现象将会提升我们列表的视觉和感觉。
原文:
Although the techniques used here are the same as the ones used in the X10 Mini, the purpose of the tutorial is not to just copy a specific look but to show you how to implement your own list view. I’m sure you all have a lot of ideas on how you would like your list to look like, behave and what to use it for.
译文:
尽管这儿用到的技术和X10 Mini中的相同,但这个课程的目的不仅仅是复制一个特定的效果,而是展示如何实现你自己的ListView。我确信你在自己ListView的外观、行为和用途上会有很多想法。
原文:
Hello AdapterView
Since we’re aiming for a list (that will show other views) we need to extend a [ViewGroup]and the most fitting of those are [AdapterView]. (The reason, or rather one reason, we’re not extending [AbsListView]is that it will not allow us to do bounce effects on the list.) So let’s start by creating a new Android project and create a class, MyListView, that extends AdapterView<Adapter>. AdapterView has four abstract methods that we need to implement: getAdapter(), setAdapter(), getSelectedView() and setSelection(). getAdapter() and setAdapter() are straight forward to implement. The other two will for the moment just throw an exception.
译文:
初识 AdapterView
既然我们的目标是一个展示其他View的列表,我们需要扩展一个[ViewGroup],它们中最适合的是 [AdapterView],(我们不继承[AbsListView]的一个原因是它不允许我们在列表中制作弹跳效果。)所以让我们创建一个新的安卓工程,并创建一个class,MyListView,它继承AdapterView<Adapter>。AdapterView有4个抽象的方法我们需要实现,分别是:getAdapter(), setAdapter(), getSelectedView() and setSelection()。getAdapter() 和 setAdapter() 直接实现,另外两个暂且各抛出一个异常.
package com.sonyericsson.tutorial.list;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
public class MyListView extends AdapterView {
/** The adapter with all the data */
private Adapter mAdapter;
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setAdapter(Adapter adapter) {
mAdapter = adapter;
removeAllViewsInLayout();
requestLayout();
}
@Override
public Adapter getAdapter() {
return mAdapter;
}
@Override
public void setSelection(int position) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public View getSelectedView() {
throw new UnsupportedOperationException("Not supported");
}
}
原文:
The only thing here worth mentioning is the setAdapter method. When we get a new adapter we clear all the views we might have had previously and then we request a layout to get and position the views from the adapter. If we at this point create a test activity and an adapter with some fake data and use our new view, we will not get anything on the screen. This is because if we want to get something on the screen we need to override the onLayout() method.
译文:
这儿唯一值得一提的是setAdapter 方法,当得到一个新的adapter的时候,我们清除所有之前已经存在的View,然后为adapter中的每个view请求一个布局和一个位置。如果这个时候我们用假数据和新View创建一个测试的Activity和一个Adapter,我们在屏幕上看不见任何东西。这是因为如果我们想在屏幕上出现一些东西,我们需要重写onLayout()方法。
原文:
Showing our first views
It is in [onLayout] where we get the views from the adapter and add them as child views.
译文:
显示我们的第一批View
在[onLayout]这个方法中我们从adapter取到View并且把它们作为子View添加进来
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// if we don't have an adapter, we don't need to do anything
if (mAdapter == null) {
return;
}
if (getChildCount() == 0) {
int position = 0;
int bottomEdge = 0;
while (bottomEdge < getHeight() && position < mAdapter.getCount()) {
View newBottomChild = mAdapter.getView(position, null, this);
addAndMeasureChild(newBottomChild);
bottomEdge += newBottomChild.getMeasuredHeight();
position++;
}
}
positionItems();
}
原文:
So, what happens here? First a call to super and a null check are performed, and then we continue with the actual code. If we haven’t added any children yet, we start by doing that. The while statement loops through the adapter until we’ve added enough views to cover the screen. When we get a view from the adapter, we start by adding it as a child and then we need to measure it in order for the view to get it’s correct size. After we’ve added all the views, we position them in the correct place.
译文:
这儿发生了什么?一个super调用和一个空指针检查被执行。然后继续执行实际的代码。如果我们没有添加过任何子view,我们开始干这个事情。while语句在adapter上循环,直到我们添加足够的view覆盖住屏幕。当从adapter中取得一个View时,我们开始把添加成一个子View,然后为了得到它的正确大小,我们需要对其进行测量。当我们添加所有的子View以后,我们把它们放置在正确的位置上。
/**
* Adds a view as a child view and takes care of measuring it
*
* @param child The view to add
*/
private void addAndMeasureChild(View child) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
addViewInLayout(child, -1, params, true);
int itemWidth = getWidth();
child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
}
/**
* Positions the children at the "correct" positions
*/
private void positionItems() {
int top = 0;
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int left = (getWidth() - width) / 2;
child.layout(left, top, left + width, top + height);
top += height;
}
}
原文:
The code here is straight forward and self-explanatory so I won’t go into details. I’ve taken some shortcuts when measuring the child view but the code works quite well in most cases. positionItems() starts at the top (0) and layouts the child views one after the other without any padding between them. Also worth noting is that we’re ignoring the possible padding that the list can have.
译文:
这一块代码是直观的,也是自我说明的,因此我不会加入细节。在测量子View的时候我采取了一些捷径,但代码在大多数情形下工作得挺好。positionItems()方法从顶部(0)开始,一个接一个地放置子View,子View之间没有任何间距。同样(译注:这些边距对我们的目标)也没有任何意义,所以我们忽略了ListView可能会有的边距。
原文:
Scrolling
If we run this code we will now get something on the screen. However, it’s not very interactive. It does not scroll when we touch the screen and we can’t click on any item. To get touch working in the list we need to override [onTouchEvent()].
译文:
滚动
如果我们运行这些代码,屏幕上会显示一些东西,然而,它是不能交互的。当我们触摸屏幕的时候,它不能滚动,我们也不能点击它上面的item。为了让touch能在ListView上起作用,我们需要重写[onTouchEvent()]。
原文:
The touch logic for just scrolling is pretty simple. When we get a down event, we save both the position of the down event and the position of the list. We will use the top of the first item to represent the list position. When we get a move event we calculate how far we are from the down event and then re-position the list using the difference in distance from the start position. If we have no child views, we just return false.
译文:
滚动的触摸逻辑非常简单,当我们得到一个down事件,我们保存down事件的位置(译注:down事件的x, y)和down事件在列表中的位置(译注:ListView中的position),我们会用第一个item的top代表ListView的位置。当我们得到一个move事件时,我们计算(译注:move事件)距离down事件的位置有多远,然后用相对于开始位置的不同距离重新定位ListView。如果没有子View, 返回false。
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getChildCount() == 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTouchStartY = (int)event.getY();
mListTopStart = getChildAt(0).getTop();
break;
case MotionEvent.ACTION_MOVE:
int scrolledDistance = (int)event.getY() - mTouchStartY;
mListTop = mListTopStart + scrolledDistance;
requestLayout();
break;
default:
break;
}
return true;
}
原文:
The position of the list is now determined by mListTop and whenever it changes we need to request a layout in order to actually re-position the views. Our previous implementation of positionItems() always started to layout from 0. Now we need to change it so that it starts from mListTop.
译文:
ListView的位置现在决定于mListTop, 无论何时当它(译注:mListTop )变化,我们需要请求一个layout 来实际重新放置子View。我们先前positionItems()的实现通常从0开始布局,现在我们需要改变它,使它从mListTop开始布局。
原文:
If we try this out now the scrolling will work fine but we can also spot some obvious problems with our list. First, the scrolling has no limits so we can scroll the list so that all items are outside of the screen. We will need some kind of limits check to prevent us from doing that. Second, if we scroll down we also see that only the items that we had from the beginning are displayed. No new items are displayed even though the adapter contains more items. We postpone the fix of the first problem to a later tutorial but let’s fix the second problem right away.
译文:
如果现在我们试一下这个,滚动将会工作,但是我们可以发现ListView的一些明显的问题。(译注:将positionItems()中的int top = 0 改成 int top = mListTop, 但我测试发现getSelectedView() 会抛出异常,需要把抛出异常的地方处理掉,例如return null)首先,滚动没有限制,所以我们可以滑动ListView,把所有item滑出屏幕。我们需要检查一些限制来阻止这样做。第二,如果我们向下滑动,我们还能看见只有在开始已经添加的item是显示的,尽管adapter有更多的item,但不会有新的item显示。我们在后面的课程解决第一个问题,但让我们来立即解决第二个问题。
原文:
Handling all the items
The reason why no new items appear when we scroll down is because of our code in onLayout(). The code there only adds views if no views haven’t already been added. One of the requirements on a list component should be that it must work just as well with ten items as it does with ten thousand items. With that in mind, we can’t just add all the items from the adapter as child views from the start, we need to make sure we handle the views efficiently. In order to be effective we should only have as many child views as we need to display the visible part of the list. It’s also a good thing to keep a small cache of views so that we can let the adapter reuse views instead of always inflating from xml.
译文:
处理所有条目
当我们向下滑动时没有新条目出现,是因为onLayout()中的代码只会在没有View被添加到ListView时才添加View(译注:这一句话原文是双重否定,比较难理解和翻译,结合代码看,只会在if (getChildCount() == 0)时才进入了add View的循环,也就是如果子view数量大于0,则不会添加新view进来)。ListView组件的一个需求应该是:它在有10,000个条目的时候,必须能和有10个条目一样流畅地运行。考虑到这一点,我们不能在一开始就添加adapter中的所有条目作为子View,我们需要确保高效地处理子View。为了达到效果,我们应当只保留ListView可见区域展示所需要的子View。还有保留一个小的View缓存也是一件好事,这样我们可以让Adapter复用view,而不是经常
从xml文件读取。
原文:
The place to handle these issues are in onLayout(). The new verson of onLayout() looks like this.
译文:
解决这个问题的地方在onLayout()方法中,新版本的onLayout()是这个样子的。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// if we don't have an adapter, we don't need to do anything
if (mAdapter == null) {
return;
}
if (getChildCount() == 0) {
mLastItemPosition = -1;
fillListDown(mListTop);
} else {
int offset = mListTop + mListTopOffset - getChildTop(getChildAt(0));
removeNonVisibleViews(offset);
fillList(offset);
}
positionItems();
invalidate();
}
原文:
fillListDown() is more or less the same thing as the while loop that was there before. Also added is a method that does the same but adds views in the top called fillListUp(). Both of these are called from fillList(). removeNonVisibleViews() removes views (at the top and the bottom) that are outside of the visible area. To keep track of the views and to be able to connect them to the correct position in the adapter, two variables have to be added: mFirstItemPosition and mLastPosition. These are the adapter positions of the first and last currently visible views. They are updated whenever we remove or add a view. Also, since the scrolling of the list is tied to the top of the first visible item, we need to update the list position whenever we add a new view at the top or remove the top view.
译文:
fillListDown()和之前那儿的while循环或多或少有些相同,还添加了一个方法fillListUp(),它做相同的事情,但是在顶部添加View。这两个都在fillList()中被调用。removeNonVisibleViews()删除在顶部和底部可见区域之外的view。为了保持追踪这些View,能够把他们连接在adapter中正确的位置上,我们不得不添加2个变量:mFirstItemPosition 和 mLastPosition。这是第一个和最后一个当前可见View在adapter中的位置,这两个变量在我们移除或者添加一个View时会更新。而且,由于列表的滑动和第一个可见条目的顶部有关,因此无论在顶部添加一个新View或者删除顶部View,我们都需要更新列表的位置。
(译注:原文没有给出填充item的具体代码,大家可以在本文结尾找到代码下载链接。)
原文:
To compensate for the fact that positionItems() will move the list up and down we need to let removeVisibleViews() and fillList() know how much the list will be moved. This is the offset variable. Otherwise we might not remove items that will move outside the visible area during positionItems() or we could forget to add items that should turn visible. Also, since mListTop is defined as the top of the first item, even if it’s not visible, we need to keep track of the distance from the currently first visible item to the place where the first item would have been.
译文:
为了弥补positionItems()将会向上和向下移动列表的事实,我们需要让removeVisibleViews()和fillList()知道列表将会移动多远,这个是偏移量变量。否则在positionItems()时可能不会移除将要移动到可见区域之外的条目,或者我们可能忘记添加应该变成可见的条目。而且,由于mListTop 是根据第一个可见条目的顶部定义的,即使这个顶部条目不可见,我们任然需要保持追踪从当前第一个可见条目到第一个条目应当所处的位置之间的距离(译注:此处可以理解成,例如当前第一个可见的条目是adapter中的第10个条目,那么在之前的向上滑动中,有[0, 10)这10个条目已经被移出了可见区域,那第一个可见条目到第一个条目应处的位置之间的距离,就是这10个被移出的条目的高度总和)。
原文:
If you’ve ever implemented an adapter, you know that to increase performance you should check and use the convertView argument instead of inflating a new view from xml everytime. Now we are implementing the other side, that is, we will call [getView()] rather than implement it, and we need to make sure we let the adapter re-use views. What we need is a cache for views that we can re-use. The standard ListView has support for different types of views but, for now, we’ll assume that all item-views are the same. As a cache we’ll just use a [LinkedList] we add it to the cache and whenever we ask the adapter getView (in fillListDown() and fillListUp()) we send in a cached view (if we have one) as convertView.
译文:
如果你曾经实现过一个adapter,你知道为了提升性能,应该检查和使用convertView 参数,而不是每次从xml文件解析一个新view。现在我们实现另一面,那就是调用[getView()],而不是实现[getView()]。并且我们需要确保让adapter复用view。我们需要给view一个可以复用的缓存,标准的ListView已经支持不同类型的View缓存,但是现在,我们假设所有的View条目都是相同类型的。我们可以使用一个泛型参数是View的 [LinkedList],当在removeNonVisibleViews()中删除一个子View,我们把这个子View添加到缓存。当我们向adapter索取View时(在fillListDown()和fillListUp()),我们发送一个缓存的View(如果我们有一个)作为convertView。
原文:
Clicking and long-pressing
For a list to be useful the items in the list need to be clickable. AdapterView implements methods that sets [OnItemClickListener] and [OnItemLongClickListener]and we should make sure to call these listeners when appropriate. To support clicking of item views we need to do three things: 1) detect a click event, 2) find the item that was clicked and 3) call the listener (if set) with the correct arguments. So let’s start from the top and implement a click-detector.
译文:
点击和长按
对于一个有用的列表,需要它里面的条目能被点击,AdapterView 实现了设置[OnItemClickListener]和 [OnItemLongClickListener] 的方法,我们需要确保在合适的时机调用这些监听(译注:listeners 的回调),
为了支持条目View的点击我们需要做3件事情:1)探测一个点击事件,2)找到被点击的条目,3)用正确的参数调用回调对象(如果设置过)。因此让我们从上到下实现一个点击探测器。
原文:
Android provides a [GestureDetector] class that can be used for this but I would actually recommend not using it. One reason is that I’ve found it to be quite unreliable, especially for long press gestures and fling gestures. Another reason is that if you delegate gesture detecting to another class you might not be able to keep track of the touch state and you will most likely need to know your touch state for a lot of things.
译文:
安卓提供了一个 [GestureDetector]类,可以被用来做这个,但实际上我不推荐使用它,一个原因是我发现它很不可靠,尤其是对长按手势和速滑手势,另一个原因是,如果你委派手势探测给另一个类,你可能无法保持跟踪触摸状态,对于很多事情你很可能需要知道你的触摸状态。
原文:
First, lets define a few touch states
译文:
首先,定义几个触摸状态
/** User is not touching the list */
private static final int TOUCH_STATE_RESTING = 0;
/** User is touching the list and right now it's still a "click" */
private static final int TOUCH_STATE_CLICK = 1;
/** User is scrolling the list */
private static final int TOUCH_STATE_SCROLL = 2;
原文:
We have previously overridden onTouchEvent() and now we are going to make some additions to handle the new states.
译文:
我们之前已经重写了onTouchEvent() ,现在我们继续做一些补充来处理新状态。
@Override
public boolean onTouchEvent(final MotionEvent event) {
if (getChildCount() == 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(event);
break;
case MotionEvent.ACTION_MOVE:
if (mTouchState == TOUCH_STATE_CLICK) {
startScrollIfNeeded(event);
}
if (mTouchState == TOUCH_STATE_SCROLL) {
scrollList((int)event.getY() - mTouchStartY);
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_CLICK) {
clickChildAt((int)event.getX(), (int)event.getY());
}
endTouch();
break;
default:
endTouch();
break;
}
return true;
}
原文:
Most of this is the same as before the additions. The code that handles down events has been refactored out to a method, startTouch(). What also happens here is that we change the state to TOUCH_STATE_CLICK. Right now we don’t know if the user intends to click or scroll the view, but until we recognize a scroll, let’s treat it as a click.
译文:
这里面的大部分和之前添加的相同,处理down事件的代码已经重构成一个方法:startTouch()。同时发生的还有,我们把状态改变成TOUCH_STATE_CLICK。现在我们还不知道用户想要点击或者滑动ListView,直到我们觉察到一个滑动(译注:操作),让我们把它当成一个点击。
原文:
The recognition of a scroll is handled in startScrollIfNeeded() which is called for move events. It compares the current touch coordinate with the coordinates for the down event and if the user has moved the finger more than a threshold value it then changes the state to TOUCH_STATE_SCROLL. I’ve used a threshold value of 10 pixels which works fine. You can also use the value from the [ViewConfiguraion] class by calling [getScaledTouchSlop()].
译文:
识别的滑动在startScrollIfNeeded()方法中处理,它在move事件中被调用,它对比当前touch坐标和down事件的坐标,如果用户手指移动已经超过一个阈值,就把状态改成TOUCH_STATE_SCROLL。我用10像素作为阈值,它工作的挺好,你还可以调用 [ViewConfiguraion]类的[getScaledTouchSlop()]来取得阈值。
原文
If we are scrolling, then it’s the same code as without the additions for click/state, though it’s now in a separate method. The list top is modified and a layout is requested to re-position the list.
译文
如果我们正在滑动,那么它和没有添加点击状态是相同的代码,尽管现在它在一个分开的方法:列表的top被修改并且请求layout来重新放置ListView(译注:就是在滑动的时候,修改ListView的mListTop,并且requestLayout())。
原文
To support clicking we need to handle ACTON_UP events as well and we also need to make sure we separate them from ACTION_CANCEL and ACTION_OUTSIDE events. For all events except down and move, we need to reset the touch state to TOUCH_STATE_RESTING, which is done in endTouch(), but it’s only for ACTION_UP that we should call the click listener. And of course, we should only call the click listener if we are in the click state and not in the scrolling state.
译文:
为了支持点击,我们需要处理ACTON_UP事件,同样还要确保从ACTION_CANCEL 和ACTION_OUTSIDE 事件中区分出它,对于所有除了down和move的事件,我们需要把touch状态重置成TOUCH_STATE_RESTING,这个操作是在endTouch()中完成的,但只有在ACTION_UP 时我们才应该调用点击回调(click listener)。当然,我们应该只在点击状态,而且不是滑动状态的时候才调用click listener。
private void clickChildAt(final int x, final int y) {
final int index = getContainingChildIndex(x, y);
if (index != INVALID_INDEX) {
final View itemView = getChildAt(index);
final int position = mFirstItemPosition + index;
final long id = mAdapter.getItemId(position);
performItemClick(itemView, position, id);
}
}
private int getContainingChildIndex(final int x, final int y) {
if (mRect == null) {
mRect = new Rect();
}
for (int index = 0; index < getChildCount(); index++) {
getChildAt(index).getHitRect(mRect);
if (mRect.contains(x, y)) {
return index;
}
}
return INVALID_INDEX;
}
原文:
clickChildAt() is responsible for calling the listener (if any) with the position for the child at the specified coordinates. To find the correct view clickChildAt() uses getContainingChildIndex() which loops through the child views and for each view checks if the coordinates given are contained within the hit-rect of the view or not.
译文:
clickChildAt()负责调用listener(如果有)(译注:监听对象),带着指定坐标的子view的位置,位置通过getContainingChildIndex()遍历每一个子View,检查指定的这个坐标是否被包含在View的点击矩形里面(译注:遍历每一个子View,看点击的坐标是否在这个子view的矩形区域内,就计算出了当前被点击的子view在列表中的位置,加上第一个可见条目在adapter中的位置,就是这个条目在adapter中的位置)。
原文:
When we have click handling in place, adding a check for long press is quite simple. A convenient way of checking for a long press is to create a [Runnable] that calls the long press listener. Then whenever we get a down event we [post] this Runnable with a delay on the view). Whenever we get an up event or when we switch to scrolling, we know it’s not going to be a long press any more so then we simply remove the Runnable by calling [removeCallbacks()]. How long to check for a long-press is up to you and the specific view you are implementing, but if it’s not something special, it’s a good idea to use the same delay as the rest of the system. Use [ViewConfiguration.getLongPressTimeout()] to get it.
译文:
我们在处理点击的地方,加一个长按检查是非常简单的。检查长按的一个简单办法就是创建一个 [Runnable],它被称作long press listener(译注:长按监听器),当得到一个down事件,我们在View上用一个延迟[post]这个Runnable ,当收到一个UP事件或者开始滑动时,我们知道用户不再打算长按,我们简单地通过调用 [removeCallbacks()] 移除这个Runnable 。长按检查时间是多长取决于你和你具体实现的View,但如果没有特殊情况,一个好的观点是和系统其它长按使用相同的延迟,使用 [ViewConfiguration.getLongPressTimeout()] 可以获取的到。
原文:
In order to be able to scroll the list even if the child views respond to touch events, you need to intercept touch events when you want to start scrolling. This is done by overriding [onInterceptTouchEvent()] which lets us monitor all the touch events passed to our children and lets us, if we want to, intercept the touch events at any point.
译文:
为了能在子view也响应touch事件的情况下滚动列表,需要当你在想滑动时候拦截touch事件,这个可以通过重写 [onInterceptTouchEvent()]实现,这个方法让我们监视所有传递给子view的touch事件,如果想要,我们可以在任何时候拦截touch事件。
@Override
public boolean onInterceptTouchEvent(final MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(event);
return false;
case MotionEvent.ACTION_MOVE:
return startScrollIfNeeded(event);
default:
endTouch();
return false;
}
}
原文:
The implementation of onInterceptTouchEvent() looks quite like onTouchEvent(). If we get a down event, we save the position of the list then return false to let the motion event pass to wherever it’s going. When we get move events, we check if we’ve moved far enough for this to be counted as a scroll move. If we have moved enough, we set the state to scrolling and then we return true to intercept future events.
译文:
onInterceptTouchEvent()的实现看起来很像onTouchEvent(),如果我们得到一个down事件,我们保存列表的位置,然后返回false,让手势事件传递到它要去的地方。当得到一个移动事件,我们检查是否移动了足够远,可以被当成是一个滑动事件。如果移动够了,我们把状态设置成scrolling 并且返回true,来拦截后面的事件。
原文
To be continued…
What we’ve made so far is a very simple list. We handle views efficiently and a user can scroll it and click and long press items. If we stop here however, we could just as well have used ListView (though we’ve learned a bit about implementing a view group). In the next part of this tutorial, we will take a look at canvas transformations and give the list a more 3D look and after that we will look into the dynamics of the list like bounce and fling effects.
译文:
待续
目前我们所做的是非常简陋的列表,我们有效地处理View,用户可以滚动列表,可以点击和长按条目,然而如果我们在这儿停止了,我们同样可以和ListView一样使用(但我们已经学到了一些关于实现view group的知识)。在这个课程的下一部分,我们将要看一下canvas 的变形,给列表一个像3D的外观,在这以后,我们将会调研列表跳跃和抛掷的动态效果。
- 评注:
这是第一部分,实现了ListView最基础的功能,滑动的时候添加和删除View,item点击和长按。但文章中展示的代码还不足以完成所述功能,可以在这里下载这一节的代码。
https://pan.baidu.com/s/1arRxt0JZErJf1l-7D_A0qA
网友评论