Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
I
ImageVideoBanner
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
jiangjiantao
ImageVideoBanner
Commits
ed47c127
Commit
ed47c127
authored
Oct 18, 2020
by
gaodapeng
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
重新写了banner,还是继续使用之前的viewpager。
parent
70319ef7
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
2174 additions
and
64 deletions
+2174
-64
ImageVideoBanner.java
...rc/main/java/com/widget/imagebanner/ImageVideoBanner.java
+64
-58
ImageVideoFragment.java
.../java/com/widget/imagebanner/view/ImageVideoFragment.java
+6
-5
ViewPager.java
.../src/main/java/com/widget/imagebanner/view/ViewPager.java
+2103
-0
banner_imge_video.xml
banner/src/main/res/layout/banner_imge_video.xml
+1
-1
No files found.
banner/src/main/java/com/widget/imagebanner/ImageVideoBanner.java
View file @
ed47c127
...
@@ -8,25 +8,24 @@ import android.os.Bundle;
...
@@ -8,25 +8,24 @@ import android.os.Bundle;
import
android.os.Handler
;
import
android.os.Handler
;
import
android.os.Message
;
import
android.os.Message
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.fragment.app.Fragment
;
import
androidx.fragment.app.FragmentActivity
;
import
androidx.fragment.app.FragmentManager
;
import
androidx.lifecycle.Lifecycle
;
import
androidx.viewpager2.adapter.FragmentStateAdapter
;
import
androidx.viewpager2.widget.ViewPager2
;
import
android.util.AttributeSet
;
import
android.util.AttributeSet
;
import
android.util.LayoutDirection
;
import
android.view.LayoutInflater
;
import
android.view.LayoutInflater
;
import
android.view.MotionEvent
;
import
android.view.MotionEvent
;
import
android.view.View
;
import
android.view.View
;
import
android.widget.FrameLayout
;
import
android.widget.FrameLayout
;
import
com.widget.imagebanner.view.ImageVideoFragment
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.fragment.app.Fragment
;
import
androidx.fragment.app.FragmentActivity
;
import
androidx.fragment.app.FragmentManager
;
import
androidx.fragment.app.FragmentStatePagerAdapter
;
import
com.widget.imagebanner.R
;
import
com.widget.imagebanner.bean.BannerBean
;
import
com.widget.imagebanner.bean.BannerBean
;
import
com.widget.imagebanner.utils.BitmapUtils
;
import
com.widget.imagebanner.utils.BitmapUtils
;
import
com.widget.imagebanner.view.ImageVideoFragment
;
import
com.widget.imagebanner.view.ViewPager
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.List
;
...
@@ -41,9 +40,9 @@ import java.util.concurrent.ScheduledExecutorService;
...
@@ -41,9 +40,9 @@ import java.util.concurrent.ScheduledExecutorService;
import
wseemann.media.FFmpegMediaMetadataRetriever
;
import
wseemann.media.FFmpegMediaMetadataRetriever
;
public
class
ImageVideoBanner
extends
FrameLayout
implements
ImageVideoFragment
.
OnVideoCompletionListener
{
public
class
ImageVideoBanner
extends
FrameLayout
implements
ViewPager
.
OnPageChangeListener
,
ImageVideoFragment
.
OnVideoCompletionListener
{
private
static
final
int
UPTATE_VIEWPAGER
=
0
;
private
static
final
int
UPTATE_VIEWPAGER
=
0
;
private
ViewPager
2
mViewPager
;
private
ViewPager
mViewPager
;
private
List
<
BannerBean
>
mList
=
new
ArrayList
<>();
private
List
<
BannerBean
>
mList
=
new
ArrayList
<>();
private
ViewsPagerAdapter
mAdapter
;
private
ViewsPagerAdapter
mAdapter
;
private
int
autoCurrIndex
=
0
;
//设置当前 第几个图片 被选中
private
int
autoCurrIndex
=
0
;
//设置当前 第几个图片 被选中
...
@@ -72,8 +71,9 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -72,8 +71,9 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
private
void
initView
(
Context
context
)
{
private
void
initView
(
Context
context
)
{
View
view
=
LayoutInflater
.
from
(
context
).
inflate
(
R
.
layout
.
banner_imge_video
,
this
,
true
);
View
view
=
LayoutInflater
.
from
(
context
).
inflate
(
R
.
layout
.
banner_imge_video
,
this
,
true
);
mViewPager
=
view
.
findViewById
(
R
.
id
.
view_pager
);
mViewPager
=
view
.
findViewById
(
R
.
id
.
view_pager
);
mAdapter
=
new
ViewsPagerAdapter
(((
FragmentActivity
)
context
).
getSupportFragmentManager
()
,
((
FragmentActivity
)
context
).
getLifecycle
()
);
mAdapter
=
new
ViewsPagerAdapter
(((
FragmentActivity
)
context
).
getSupportFragmentManager
());
mViewPager
.
setAdapter
(
mAdapter
);
mViewPager
.
setAdapter
(
mAdapter
);
mViewPager
.
setOnPageChangeListener
(
this
);
//ViewPager手势滑动禁用
//ViewPager手势滑动禁用
mViewPager
.
setOnTouchListener
(
new
OnTouchListener
()
{
mViewPager
.
setOnTouchListener
(
new
OnTouchListener
()
{
@Override
@Override
...
@@ -81,18 +81,15 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -81,18 +81,15 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
return
true
;
return
true
;
}
}
});
});
mViewPager
.
registerOnPageChangeCallback
(
callback
);
}
}
public
void
setData
(
List
<
com
.
widget
.
imagebanner
.
bean
.
BannerBean
>
listBean
)
{
public
void
replaceData
(
List
<
BannerBean
>
listBean
)
{
if
(
listBean
!=
null
)
{
mAdapter
.
replaceData
(
listBean
);
mAdapter
.
setData
(
listBean
);
}
}
}
public
void
addData
(
List
<
com
.
widget
.
imagebanner
.
bean
.
BannerBean
>
listBean
)
{
public
void
addData
(
List
<
BannerBean
>
listBean
)
{
mAdapter
.
addData
(
listBean
);
mAdapter
.
addData
(
listBean
);
}
}
...
@@ -132,6 +129,7 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -132,6 +129,7 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
mmr
.
extractMetadata
(
FFmpegMediaMetadataRetriever
.
METADATA_KEY_ARTIST
);
mmr
.
extractMetadata
(
FFmpegMediaMetadataRetriever
.
METADATA_KEY_ARTIST
);
Bitmap
b
=
mmr
.
getFrameAtTime
();
// frame at 2 seconds
Bitmap
b
=
mmr
.
getFrameAtTime
();
// frame at 2 seconds
bannerBean
.
setLoadingImage
(
BitmapUtils
.
getBytes
(
b
));
bannerBean
.
setLoadingImage
(
BitmapUtils
.
getBytes
(
b
));
b
.
recycle
();
mmr
.
release
();
mmr
.
release
();
}
}
return
true
;
return
true
;
...
@@ -149,7 +147,6 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -149,7 +147,6 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
}
}
}
}
private
ViewPager2
.
OnPageChangeCallback
callback
=
new
ViewPager2
.
OnPageChangeCallback
()
{
@Override
@Override
public
void
onPageScrolled
(
int
position
,
float
positionOffset
,
int
positionOffsetPixels
)
{
public
void
onPageScrolled
(
int
position
,
float
positionOffset
,
int
positionOffsetPixels
)
{
...
@@ -170,8 +167,6 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -170,8 +167,6 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
public
void
onPageScrollStateChanged
(
int
state
)
{
public
void
onPageScrollStateChanged
(
int
state
)
{
}
}
};
//定时轮播图片,需要在主线程里面修改 UI
//定时轮播图片,需要在主线程里面修改 UI
private
Handler
mHandler
=
new
Handler
()
{
private
Handler
mHandler
=
new
Handler
()
{
...
@@ -209,12 +204,10 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -209,12 +204,10 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
public
void
startBanner
()
{
public
void
startBanner
()
{
//如果第一页是视频 不用定时器
//如果第一页是视频 不用定时器
if
(
mList
.
size
()
>
0
)
{
if
(
mList
.
get
(
0
).
getType
()
==
0
)
{
if
(
mList
.
get
(
0
).
getType
()
==
0
)
{
startBanner
(
period
);
startBanner
(
period
);
}
}
}
}
}
public
void
startBanner
(
long
delay
)
{
public
void
startBanner
(
long
delay
)
{
stopBanner
();
stopBanner
();
...
@@ -252,22 +245,20 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -252,22 +245,20 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
startBanner
(
0
);
startBanner
(
0
);
}
}
private
final
class
ViewsPagerAdapter
extends
FragmentStateAdapter
{
private
final
class
ViewsPagerAdapter
extends
FragmentState
Pager
Adapter
{
private
ImageVideoFragment
fragment
;
private
ImageVideoFragment
fragment
;
private
FragmentManager
fm
;
private
FragmentManager
fm
;
public
ViewsPagerAdapter
(
@NonNull
FragmentManager
fragmentManager
,
@NonNull
Lifecycle
lifecycle
)
{
public
ViewsPagerAdapter
(
FragmentManager
fm
)
{
super
(
f
ragmentManager
,
lifecycle
);
super
(
f
m
);
this
.
fm
=
f
ragmentManager
;
this
.
fm
=
f
m
;
}
}
@NonNull
@Override
@Override
public
Fragment
createFragment
(
int
position
)
{
public
Fragment
getItem
(
int
position
)
{
fragment
=
new
ImageVideoFragment
();
fragment
=
new
ImageVideoFragment
();
fragment
.
setOnVideoCompletionListener
(
ImageVideoBanner
.
this
);
fragment
.
setOnVideoCompletionListener
(
ImageVideoBanner
.
this
);
com
.
widget
.
imagebanner
.
bean
.
BannerBean
bannerBean
=
mList
.
get
(
position
);
BannerBean
bannerBean
=
mList
.
get
(
position
);
Bundle
bundle
=
new
Bundle
();
Bundle
bundle
=
new
Bundle
();
bundle
.
putSerializable
(
"bannerBean"
,
bannerBean
);
bundle
.
putSerializable
(
"bannerBean"
,
bannerBean
);
bundle
.
putBoolean
(
"loop"
,
getLoop
());
bundle
.
putBoolean
(
"loop"
,
getLoop
());
...
@@ -275,11 +266,6 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -275,11 +266,6 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
return
fragment
;
return
fragment
;
}
}
@Override
public
int
getItemCount
()
{
return
mList
.
size
();
}
/**
/**
* 只有1个视频的时候循环播放
* 只有1个视频的时候循环播放
*
*
...
@@ -292,17 +278,37 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
...
@@ -292,17 +278,37 @@ public class ImageVideoBanner extends FrameLayout implements ImageVideoFragment.
return
false
;
return
false
;
}
}
public
void
setData
(
@NonNull
List
<
BannerBean
>
listBean
)
{
@Override
public
int
getCount
()
{
return
mList
.
size
();
}
@Override
public
int
getItemPosition
(
Object
object
)
{
if
(
mList
.
size
()
>
0
)
{
return
POSITION_NONE
;
}
return
super
.
getItemPosition
(
object
);
}
public
void
replaceData
(
List
<
BannerBean
>
listBean
)
{
// 新数据和原来数据对比,不一致才去刷新
// 新数据和原来数据对比,不一致才去刷新
boolean
change
=
compareData
(
listBean
);
boolean
change
=
compareData
(
listBean
);
if
(!
change
)
{
if
(!
change
)
{
notifyDataSetChanged
();
notifyDataSetChanged
();
// startBanner();
return
;
return
;
}
}
if
(
null
!=
listBean
)
{
mList
.
clear
();
mList
.
clear
();
addData
(
listBean
);
addData
(
listBean
);
notifyDataSetChanged
();
//如果第一张是图片 需要开启定时器
if
(
listBean
.
get
(
0
).
getType
()
==
0
)
{
startBanner
();
}
autoCurrIndex
=
0
;
autoCurrIndex
=
0
;
// startBanner();
}
}
}
...
...
banner/src/main/java/com/widget/imagebanner/view/ImageVideoFragment.java
View file @
ed47c127
...
@@ -7,10 +7,6 @@ import android.os.Bundle;
...
@@ -7,10 +7,6 @@ import android.os.Bundle;
import
android.os.Handler
;
import
android.os.Handler
;
import
android.os.Message
;
import
android.os.Message
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.fragment.app.Fragment
;
import
android.util.Log
;
import
android.util.Log
;
import
android.view.LayoutInflater
;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.View
;
...
@@ -19,12 +15,17 @@ import android.widget.ImageView;
...
@@ -19,12 +15,17 @@ import android.widget.ImageView;
import
android.widget.LinearLayout
;
import
android.widget.LinearLayout
;
import
android.widget.VideoView
;
import
android.widget.VideoView
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.fragment.app.Fragment
;
import
com.bumptech.glide.Glide
;
import
com.bumptech.glide.Glide
;
import
com.widget.imagebanner.R
;
import
com.widget.imagebanner.R
;
import
com.widget.imagebanner.bean.BannerBean
;
import
com.widget.imagebanner.bean.BannerBean
;
import
com.widget.imagebanner.utils.BannerLogFileUtils
;
import
com.widget.imagebanner.utils.BannerLogFileUtils
;
import
com.widget.imagebanner.utils.BitmapUtils
;
import
com.widget.imagebanner.utils.BitmapUtils
;
import
java.io.File
;
import
java.io.File
;
...
@@ -268,7 +269,7 @@ public class ImageVideoFragment extends Fragment {
...
@@ -268,7 +269,7 @@ public class ImageVideoFragment extends Fragment {
private
void
pausePlayer
()
{
private
void
pausePlayer
()
{
if
(
null
!=
mVideoView
)
{
if
(
null
!=
mVideoView
)
{
Log
.
e
(
TAG
,
"pausePlayer 当前进度是"
+
mVideoView
.
getCurrentPosition
());
Log
.
e
(
TAG
,
"pausePlayer 当前进度是"
+
mVideoView
.
getCurrentPosition
());
mVideoView
.
setBackgroundColor
(
getResources
().
getColor
(
android
.
R
.
color
.
white
)
);
mVideoView
.
setBackgroundColor
(
Color
.
WHITE
);
playerPaused
=
true
;
playerPaused
=
true
;
this
.
currentPosition
=
mVideoView
.
getCurrentPosition
();
this
.
currentPosition
=
mVideoView
.
getCurrentPosition
();
mVideoView
.
pause
();
mVideoView
.
pause
();
...
...
banner/src/main/java/com/widget/imagebanner/view/ViewPager.java
0 → 100644
View file @
ed47c127
/*
* Copyright (C) 2011 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
.
widget
.
imagebanner
.
view
;
import
android.content.Context
;
import
android.content.res.TypedArray
;
import
android.database.DataSetObserver
;
import
android.graphics.Canvas
;
import
android.graphics.Rect
;
import
android.graphics.drawable.Drawable
;
import
android.os.Build
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
android.os.SystemClock
;
import
android.util.AttributeSet
;
import
android.util.Log
;
import
android.view.FocusFinder
;
import
android.view.Gravity
;
import
android.view.KeyEvent
;
import
android.view.MotionEvent
;
import
android.view.SoundEffectConstants
;
import
android.view.VelocityTracker
;
import
android.view.View
;
import
android.view.ViewConfiguration
;
import
android.view.ViewGroup
;
import
android.view.ViewParent
;
import
android.view.accessibility.AccessibilityEvent
;
import
android.view.animation.Interpolator
;
import
android.widget.Scroller
;
import
androidx.core.os.ParcelableCompat
;
import
androidx.core.os.ParcelableCompatCreatorCallbacks
;
import
androidx.core.view.MotionEventCompat
;
import
androidx.core.view.VelocityTrackerCompat
;
import
androidx.core.view.ViewCompat
;
import
androidx.core.view.ViewConfigurationCompat
;
import
androidx.core.widget.EdgeEffectCompat
;
import
androidx.viewpager.widget.PagerAdapter
;
import
com.widget.imagebanner.event.KeyEventCompat
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Comparator
;
/**
* Layout manager that allows the user to flip left and right
* through pages of data. You supply an implementation of a
* {@link PagerAdapter} to generate the pages that the view shows.
*
* <p>Note this class is currently under early design and
* development. The API will likely change in later updates of
* the compatibility library, requiring changes to the source code
* of apps when they are compiled against the newer version.</p>
*
* <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
* which is a convenient way to supply and manage the lifecycle of each page.
* There are standard adapters implemented for using fragments with the ViewPager,
* which cover the most common use cases. These are
* {@link androidx.fragment.app.FragmentPagerAdapter},
* {@link androidx.fragment.app.FragmentStatePagerAdapter}; each of these
* classes have simple code showing how to build a full user interface
* with them.
*
* <p>Here is a more complicated example of ViewPager, using it in conjuction
* with {@link android.app.ActionBar} tabs. You can find other examples of using
* ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
*
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
* complete}
*/
public
class
ViewPager
extends
ViewGroup
{
private
static
final
String
TAG
=
"ViewPager"
;
private
static
final
boolean
DEBUG
=
false
;
private
static
final
boolean
USE_CACHE
=
false
;
private
static
final
int
DEFAULT_OFFSCREEN_PAGES
=
0
;
//默认是1,这里直接改为0
private
static
final
int
MAX_SETTLE_DURATION
=
600
;
// ms
private
static
final
int
MIN_DISTANCE_FOR_FLING
=
25
;
// dips
private
static
final
int
[]
LAYOUT_ATTRS
=
new
int
[]
{
android
.
R
.
attr
.
layout_gravity
};
static
class
ItemInfo
{
Object
object
;
int
position
;
boolean
scrolling
;
}
private
static
final
Comparator
<
ItemInfo
>
COMPARATOR
=
new
Comparator
<
ItemInfo
>(){
@Override
public
int
compare
(
ItemInfo
lhs
,
ItemInfo
rhs
)
{
return
lhs
.
position
-
rhs
.
position
;
}};
private
static
final
Interpolator
sInterpolator
=
new
Interpolator
()
{
public
float
getInterpolation
(
float
t
)
{
t
-=
1.0f
;
return
t
*
t
*
t
*
t
*
t
+
1.0f
;
}
};
private
final
ArrayList
<
ItemInfo
>
mItems
=
new
ArrayList
<
ItemInfo
>();
private
PagerAdapter
mAdapter
;
private
int
mCurItem
;
// Index of currently displayed page.
private
int
mRestoredCurItem
=
-
1
;
private
Parcelable
mRestoredAdapterState
=
null
;
private
ClassLoader
mRestoredClassLoader
=
null
;
private
Scroller
mScroller
;
private
PagerObserver
mObserver
;
private
int
mPageMargin
;
private
Drawable
mMarginDrawable
;
private
int
mTopPageBounds
;
private
int
mBottomPageBounds
;
private
int
mChildWidthMeasureSpec
;
private
int
mChildHeightMeasureSpec
;
private
boolean
mInLayout
;
private
boolean
mScrollingCacheEnabled
;
private
boolean
mPopulatePending
;
private
boolean
mScrolling
;
private
int
mOffscreenPageLimit
=
DEFAULT_OFFSCREEN_PAGES
;
private
boolean
mIsBeingDragged
;
private
boolean
mIsUnableToDrag
;
private
int
mTouchSlop
;
private
float
mInitialMotionX
;
/**
* Position of the last motion event.
*/
private
float
mLastMotionX
;
private
float
mLastMotionY
;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private
int
mActivePointerId
=
INVALID_POINTER
;
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
*/
private
static
final
int
INVALID_POINTER
=
-
1
;
/**
* Determines speed during touch scrolling
*/
private
VelocityTracker
mVelocityTracker
;
private
int
mMinimumVelocity
;
private
int
mMaximumVelocity
;
private
int
mFlingDistance
;
private
boolean
mFakeDragging
;
private
long
mFakeDragBeginTime
;
private
EdgeEffectCompat
mLeftEdge
;
private
EdgeEffectCompat
mRightEdge
;
private
boolean
mFirstLayout
=
true
;
private
boolean
mCalledSuper
;
private
int
mDecorChildCount
;
private
OnPageChangeListener
mOnPageChangeListener
;
private
OnPageChangeListener
mInternalPageChangeListener
;
private
OnAdapterChangeListener
mAdapterChangeListener
;
/**
* Indicates that the pager is in an idle, settled state. The current page
* is fully in view and no animation is in progress.
*/
public
static
final
int
SCROLL_STATE_IDLE
=
0
;
/**
* Indicates that the pager is currently being dragged by the user.
*/
public
static
final
int
SCROLL_STATE_DRAGGING
=
1
;
/**
* Indicates that the pager is in the process of settling to a final position.
*/
public
static
final
int
SCROLL_STATE_SETTLING
=
2
;
private
int
mScrollState
=
SCROLL_STATE_IDLE
;
/**
* Callback interface for responding to changing state of the selected page.
*/
public
interface
OnPageChangeListener
{
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
*/
public
void
onPageScrolled
(
int
position
,
float
positionOffset
,
int
positionOffsetPixels
);
/**
* This method will be invoked when a new page becomes selected. Animation is not
* necessarily complete.
*
* @param position Position index of the new selected page.
*/
public
void
onPageSelected
(
int
position
);
/**
* Called when the scroll state changes. Useful for discovering when the user
* begins dragging, when the pager is automatically settling to the current page,
* or when it is fully stopped/idle.
*
* @param state The new scroll state.
* @see ViewPager#SCROLL_STATE_IDLE
* @see ViewPager#SCROLL_STATE_DRAGGING
* @see ViewPager#SCROLL_STATE_SETTLING
*/
public
void
onPageScrollStateChanged
(
int
state
);
}
/**
* Simple implementation of the {@link OnPageChangeListener} interface with stub
* implementations of each method. Extend this if you do not intend to override
* every method of {@link OnPageChangeListener}.
*/
public
static
class
SimpleOnPageChangeListener
implements
OnPageChangeListener
{
@Override
public
void
onPageScrolled
(
int
position
,
float
positionOffset
,
int
positionOffsetPixels
)
{
// This space for rent
}
@Override
public
void
onPageSelected
(
int
position
)
{
// This space for rent
}
@Override
public
void
onPageScrollStateChanged
(
int
state
)
{
// This space for rent
}
}
/**
* Used internally to monitor when adapters are switched.
*/
interface
OnAdapterChangeListener
{
public
void
onAdapterChanged
(
PagerAdapter
oldAdapter
,
PagerAdapter
newAdapter
);
}
/**
* Used internally to tag special types of child views that should be added as
* pager decorations by default.
*/
interface
Decor
{}
public
ViewPager
(
Context
context
)
{
super
(
context
);
initViewPager
();
}
public
ViewPager
(
Context
context
,
AttributeSet
attrs
)
{
super
(
context
,
attrs
);
initViewPager
();
}
void
initViewPager
()
{
setWillNotDraw
(
false
);
setDescendantFocusability
(
FOCUS_AFTER_DESCENDANTS
);
setFocusable
(
true
);
final
Context
context
=
getContext
();
mScroller
=
new
Scroller
(
context
,
sInterpolator
);
final
ViewConfiguration
configuration
=
ViewConfiguration
.
get
(
context
);
mTouchSlop
=
ViewConfigurationCompat
.
getScaledPagingTouchSlop
(
configuration
);
mMinimumVelocity
=
configuration
.
getScaledMinimumFlingVelocity
();
mMaximumVelocity
=
configuration
.
getScaledMaximumFlingVelocity
();
mLeftEdge
=
new
EdgeEffectCompat
(
context
);
mRightEdge
=
new
EdgeEffectCompat
(
context
);
final
float
density
=
context
.
getResources
().
getDisplayMetrics
().
density
;
mFlingDistance
=
(
int
)
(
MIN_DISTANCE_FOR_FLING
*
density
);
}
private
void
setScrollState
(
int
newState
)
{
if
(
mScrollState
==
newState
)
{
return
;
}
mScrollState
=
newState
;
if
(
mOnPageChangeListener
!=
null
)
{
mOnPageChangeListener
.
onPageScrollStateChanged
(
newState
);
}
}
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public
void
setAdapter
(
PagerAdapter
adapter
)
{
if
(
mAdapter
!=
null
)
{
mAdapter
.
unregisterDataSetObserver
(
mObserver
);
mAdapter
.
startUpdate
(
this
);
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
final
ItemInfo
ii
=
mItems
.
get
(
i
);
mAdapter
.
destroyItem
(
this
,
ii
.
position
,
ii
.
object
);
}
mAdapter
.
finishUpdate
(
this
);
mItems
.
clear
();
removeNonDecorViews
();
mCurItem
=
0
;
scrollTo
(
0
,
0
);
}
final
PagerAdapter
oldAdapter
=
mAdapter
;
mAdapter
=
adapter
;
if
(
mAdapter
!=
null
)
{
if
(
mObserver
==
null
)
{
mObserver
=
new
PagerObserver
();
}
mAdapter
.
registerDataSetObserver
(
mObserver
);
mPopulatePending
=
false
;
if
(
mRestoredCurItem
>=
0
)
{
mAdapter
.
restoreState
(
mRestoredAdapterState
,
mRestoredClassLoader
);
setCurrentItemInternal
(
mRestoredCurItem
,
false
,
true
);
mRestoredCurItem
=
-
1
;
mRestoredAdapterState
=
null
;
mRestoredClassLoader
=
null
;
}
else
{
populate
();
}
}
if
(
mAdapterChangeListener
!=
null
&&
oldAdapter
!=
adapter
)
{
mAdapterChangeListener
.
onAdapterChanged
(
oldAdapter
,
adapter
);
}
}
private
void
removeNonDecorViews
()
{
for
(
int
i
=
0
;
i
<
getChildCount
();
i
++)
{
final
View
child
=
getChildAt
(
i
);
final
LayoutParams
lp
=
(
LayoutParams
)
child
.
getLayoutParams
();
if
(!
lp
.
isDecor
)
{
removeViewAt
(
i
);
i
--;
}
}
}
/**
* Retrieve the current adapter supplying pages.
*
* @return The currently registered PagerAdapter
*/
public
PagerAdapter
getAdapter
()
{
return
mAdapter
;
}
void
setOnAdapterChangeListener
(
OnAdapterChangeListener
listener
)
{
mAdapterChangeListener
=
listener
;
}
/**
* Set the currently selected page. If the ViewPager has already been through its first
* layout there will be a smooth animated transition between the current item and the
* specified item.
*
* @param item Item index to select
*/
public
void
setCurrentItem
(
int
item
)
{
mPopulatePending
=
false
;
setCurrentItemInternal
(
item
,
!
mFirstLayout
,
false
);
}
/**
* Set the currently selected page.
*
* @param item Item index to select
* @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
*/
public
void
setCurrentItem
(
int
item
,
boolean
smoothScroll
)
{
mPopulatePending
=
false
;
setCurrentItemInternal
(
item
,
smoothScroll
,
false
);
}
public
int
getCurrentItem
()
{
return
mCurItem
;
}
void
setCurrentItemInternal
(
int
item
,
boolean
smoothScroll
,
boolean
always
)
{
setCurrentItemInternal
(
item
,
smoothScroll
,
always
,
0
);
}
void
setCurrentItemInternal
(
int
item
,
boolean
smoothScroll
,
boolean
always
,
int
velocity
)
{
if
(
mAdapter
==
null
||
mAdapter
.
getCount
()
<=
0
)
{
setScrollingCacheEnabled
(
false
);
return
;
}
if
(!
always
&&
mCurItem
==
item
&&
mItems
.
size
()
!=
0
)
{
setScrollingCacheEnabled
(
false
);
return
;
}
if
(
item
<
0
)
{
item
=
0
;
}
else
if
(
item
>=
mAdapter
.
getCount
())
{
item
=
mAdapter
.
getCount
()
-
1
;
}
final
int
pageLimit
=
mOffscreenPageLimit
;
if
(
item
>
(
mCurItem
+
pageLimit
)
||
item
<
(
mCurItem
-
pageLimit
))
{
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
mItems
.
get
(
i
).
scrolling
=
true
;
}
}
final
boolean
dispatchSelected
=
mCurItem
!=
item
;
mCurItem
=
item
;
populate
();
final
int
destX
=
(
getWidth
()
+
mPageMargin
)
*
item
;
if
(
smoothScroll
)
{
smoothScrollTo
(
destX
,
0
,
velocity
);
if
(
dispatchSelected
&&
mOnPageChangeListener
!=
null
)
{
mOnPageChangeListener
.
onPageSelected
(
item
);
}
if
(
dispatchSelected
&&
mInternalPageChangeListener
!=
null
)
{
mInternalPageChangeListener
.
onPageSelected
(
item
);
}
}
else
{
if
(
dispatchSelected
&&
mOnPageChangeListener
!=
null
)
{
mOnPageChangeListener
.
onPageSelected
(
item
);
}
if
(
dispatchSelected
&&
mInternalPageChangeListener
!=
null
)
{
mInternalPageChangeListener
.
onPageSelected
(
item
);
}
completeScroll
();
scrollTo
(
destX
,
0
);
}
}
/**
* Set a listener that will be invoked whenever the page changes or is incrementally
* scrolled. See {@link OnPageChangeListener}.
*
* @param listener Listener to set
*/
public
void
setOnPageChangeListener
(
OnPageChangeListener
listener
)
{
mOnPageChangeListener
=
listener
;
}
/**
* Set a separate OnPageChangeListener for internal use by the support library.
*
* @param listener Listener to set
* @return The old listener that was set, if any.
*/
OnPageChangeListener
setInternalPageChangeListener
(
OnPageChangeListener
listener
)
{
OnPageChangeListener
oldListener
=
mInternalPageChangeListener
;
mInternalPageChangeListener
=
listener
;
return
oldListener
;
}
/**
* Returns the number of pages that will be retained to either side of the
* current page in the view hierarchy in an idle state. Defaults to 1.
*
* @return How many pages will be kept offscreen on either side
* @see #setOffscreenPageLimit(int)
*/
public
int
getOffscreenPageLimit
()
{
return
mOffscreenPageLimit
;
}
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
*
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
public
void
setOffscreenPageLimit
(
int
limit
)
{
if
(
limit
<
DEFAULT_OFFSCREEN_PAGES
)
{
Log
.
w
(
TAG
,
"Requested offscreen page limit "
+
limit
+
" too small; defaulting to "
+
DEFAULT_OFFSCREEN_PAGES
);
limit
=
DEFAULT_OFFSCREEN_PAGES
;
}
if
(
limit
!=
mOffscreenPageLimit
)
{
mOffscreenPageLimit
=
limit
;
populate
();
}
}
/**
* Set the margin between pages.
*
* @param marginPixels Distance between adjacent pages in pixels
* @see #getPageMargin()
* @see #setPageMarginDrawable(Drawable)
* @see #setPageMarginDrawable(int)
*/
public
void
setPageMargin
(
int
marginPixels
)
{
final
int
oldMargin
=
mPageMargin
;
mPageMargin
=
marginPixels
;
final
int
width
=
getWidth
();
recomputeScrollPosition
(
width
,
width
,
marginPixels
,
oldMargin
);
requestLayout
();
}
/**
* Return the margin between pages.
*
* @return The size of the margin in pixels
*/
public
int
getPageMargin
()
{
return
mPageMargin
;
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param d Drawable to display between pages
*/
public
void
setPageMarginDrawable
(
Drawable
d
)
{
mMarginDrawable
=
d
;
if
(
d
!=
null
)
refreshDrawableState
();
setWillNotDraw
(
d
==
null
);
invalidate
();
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param resId Resource ID of a drawable to display between pages
*/
public
void
setPageMarginDrawable
(
int
resId
)
{
setPageMarginDrawable
(
getContext
().
getResources
().
getDrawable
(
resId
));
}
@Override
protected
boolean
verifyDrawable
(
Drawable
who
)
{
return
super
.
verifyDrawable
(
who
)
||
who
==
mMarginDrawable
;
}
@Override
protected
void
drawableStateChanged
()
{
super
.
drawableStateChanged
();
final
Drawable
d
=
mMarginDrawable
;
if
(
d
!=
null
&&
d
.
isStateful
())
{
d
.
setState
(
getDrawableState
());
}
}
// We want the duration of the page snap animation to be influenced by the distance that
// the screen has to travel, however, we don't want this duration to be effected in a
// purely linear fashion. Instead, we use this method to moderate the effect that the distance
// of travel has on the overall snap duration.
float
distanceInfluenceForSnapDuration
(
float
f
)
{
f
-=
0.5f
;
// center the values about 0.
f
*=
0.3f
*
Math
.
PI
/
2.0f
;
return
(
float
)
Math
.
sin
(
f
);
}
/**
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
*/
void
smoothScrollTo
(
int
x
,
int
y
)
{
smoothScrollTo
(
x
,
y
,
0
);
}
/**
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
* @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
*/
void
smoothScrollTo
(
int
x
,
int
y
,
int
velocity
)
{
if
(
getChildCount
()
==
0
)
{
// Nothing to do.
setScrollingCacheEnabled
(
false
);
return
;
}
int
sx
=
getScrollX
();
int
sy
=
getScrollY
();
int
dx
=
x
-
sx
;
int
dy
=
y
-
sy
;
if
(
dx
==
0
&&
dy
==
0
)
{
completeScroll
();
setScrollState
(
SCROLL_STATE_IDLE
);
return
;
}
setScrollingCacheEnabled
(
true
);
mScrolling
=
true
;
setScrollState
(
SCROLL_STATE_SETTLING
);
final
int
width
=
getWidth
();
final
int
halfWidth
=
width
/
2
;
final
float
distanceRatio
=
Math
.
min
(
1
f
,
1.0f
*
Math
.
abs
(
dx
)
/
width
);
final
float
distance
=
halfWidth
+
halfWidth
*
distanceInfluenceForSnapDuration
(
distanceRatio
);
int
duration
=
0
;
velocity
=
Math
.
abs
(
velocity
);
if
(
velocity
>
0
)
{
duration
=
4
*
Math
.
round
(
1000
*
Math
.
abs
(
distance
/
velocity
));
}
else
{
final
float
pageDelta
=
(
float
)
Math
.
abs
(
dx
)
/
(
width
+
mPageMargin
);
duration
=
(
int
)
((
pageDelta
+
1
)
*
100
);
}
duration
=
Math
.
min
(
duration
,
MAX_SETTLE_DURATION
);
mScroller
.
startScroll
(
sx
,
sy
,
dx
,
dy
,
duration
);
invalidate
();
}
void
addNewItem
(
int
position
,
int
index
)
{
ItemInfo
ii
=
new
ItemInfo
();
ii
.
position
=
position
;
ii
.
object
=
mAdapter
.
instantiateItem
(
this
,
position
);
if
(
index
<
0
)
{
mItems
.
add
(
ii
);
}
else
{
mItems
.
add
(
index
,
ii
);
}
}
void
dataSetChanged
()
{
// This method only gets called if our observer is attached, so mAdapter is non-null.
boolean
needPopulate
=
mItems
.
size
()
<
3
&&
mItems
.
size
()
<
mAdapter
.
getCount
();
int
newCurrItem
=
-
1
;
boolean
isUpdating
=
false
;
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
final
ItemInfo
ii
=
mItems
.
get
(
i
);
final
int
newPos
=
mAdapter
.
getItemPosition
(
ii
.
object
);
if
(
newPos
==
PagerAdapter
.
POSITION_UNCHANGED
)
{
continue
;
}
if
(
newPos
==
PagerAdapter
.
POSITION_NONE
)
{
mItems
.
remove
(
i
);
i
--;
if
(!
isUpdating
)
{
mAdapter
.
startUpdate
(
this
);
isUpdating
=
true
;
}
mAdapter
.
destroyItem
(
this
,
ii
.
position
,
ii
.
object
);
needPopulate
=
true
;
if
(
mCurItem
==
ii
.
position
)
{
// Keep the current item in the valid range
newCurrItem
=
Math
.
max
(
0
,
Math
.
min
(
mCurItem
,
mAdapter
.
getCount
()
-
1
));
}
continue
;
}
if
(
ii
.
position
!=
newPos
)
{
if
(
ii
.
position
==
mCurItem
)
{
// Our current item changed position. Follow it.
newCurrItem
=
newPos
;
}
ii
.
position
=
newPos
;
needPopulate
=
true
;
}
}
if
(
isUpdating
)
{
mAdapter
.
finishUpdate
(
this
);
}
Collections
.
sort
(
mItems
,
COMPARATOR
);
if
(
newCurrItem
>=
0
)
{
// TODO This currently causes a jump.
setCurrentItemInternal
(
newCurrItem
,
false
,
true
);
needPopulate
=
true
;
}
if
(
needPopulate
)
{
populate
();
requestLayout
();
}
}
void
populate
()
{
if
(
mAdapter
==
null
)
{
return
;
}
// Bail now if we are waiting to populate. This is to hold off
// on creating views from the time the user releases their finger to
// fling to a new position until we have finished the scroll to
// that position, avoiding glitches from happening at that point.
if
(
mPopulatePending
)
{
if
(
DEBUG
)
Log
.
i
(
TAG
,
"populate is pending, skipping for now..."
);
return
;
}
// Also, don't populate until we are attached to a window. This is to
// avoid trying to populate before we have restored our view hierarchy
// state and conflicting with what is restored.
if
(
getWindowToken
()
==
null
)
{
return
;
}
mAdapter
.
startUpdate
(
this
);
final
int
pageLimit
=
mOffscreenPageLimit
;
final
int
startPos
=
Math
.
max
(
0
,
mCurItem
-
pageLimit
);
final
int
N
=
mAdapter
.
getCount
();
final
int
endPos
=
Math
.
min
(
N
-
1
,
mCurItem
+
pageLimit
);
if
(
DEBUG
)
Log
.
v
(
TAG
,
"populating: startPos="
+
startPos
+
" endPos="
+
endPos
);
// Add and remove pages in the existing list.
int
lastPos
=
-
1
;
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
ItemInfo
ii
=
mItems
.
get
(
i
);
if
((
ii
.
position
<
startPos
||
ii
.
position
>
endPos
)
&&
!
ii
.
scrolling
)
{
if
(
DEBUG
)
Log
.
i
(
TAG
,
"removing: "
+
ii
.
position
+
" @ "
+
i
);
mItems
.
remove
(
i
);
i
--;
mAdapter
.
destroyItem
(
this
,
ii
.
position
,
ii
.
object
);
}
else
if
(
lastPos
<
endPos
&&
ii
.
position
>
startPos
)
{
// The next item is outside of our range, but we have a gap
// between it and the last item where we want to have a page
// shown. Fill in the gap.
lastPos
++;
if
(
lastPos
<
startPos
)
{
lastPos
=
startPos
;
}
while
(
lastPos
<=
endPos
&&
lastPos
<
ii
.
position
)
{
if
(
DEBUG
)
Log
.
i
(
TAG
,
"inserting: "
+
lastPos
+
" @ "
+
i
);
addNewItem
(
lastPos
,
i
);
lastPos
++;
i
++;
}
}
lastPos
=
ii
.
position
;
}
// Add any new pages we need at the end.
lastPos
=
mItems
.
size
()
>
0
?
mItems
.
get
(
mItems
.
size
()-
1
).
position
:
-
1
;
if
(
lastPos
<
endPos
)
{
lastPos
++;
lastPos
=
lastPos
>
startPos
?
lastPos
:
startPos
;
while
(
lastPos
<=
endPos
)
{
if
(
DEBUG
)
Log
.
i
(
TAG
,
"appending: "
+
lastPos
);
addNewItem
(
lastPos
,
-
1
);
lastPos
++;
}
}
if
(
DEBUG
)
{
Log
.
i
(
TAG
,
"Current page list:"
);
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
Log
.
i
(
TAG
,
"#"
+
i
+
": page "
+
mItems
.
get
(
i
).
position
);
}
}
ItemInfo
curItem
=
null
;
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
if
(
mItems
.
get
(
i
).
position
==
mCurItem
)
{
curItem
=
mItems
.
get
(
i
);
break
;
}
}
mAdapter
.
setPrimaryItem
(
this
,
mCurItem
,
curItem
!=
null
?
curItem
.
object
:
null
);
mAdapter
.
finishUpdate
(
this
);
if
(
hasFocus
())
{
View
currentFocused
=
findFocus
();
ItemInfo
ii
=
currentFocused
!=
null
?
infoForAnyChild
(
currentFocused
)
:
null
;
if
(
ii
==
null
||
ii
.
position
!=
mCurItem
)
{
for
(
int
i
=
0
;
i
<
getChildCount
();
i
++)
{
View
child
=
getChildAt
(
i
);
ii
=
infoForChild
(
child
);
if
(
ii
!=
null
&&
ii
.
position
==
mCurItem
)
{
if
(
child
.
requestFocus
(
FOCUS_FORWARD
))
{
break
;
}
}
}
}
}
}
/**
* This is the persistent state that is saved by ViewPager. Only needed
* if you are creating a sublass of ViewPager that must save its own
* state, in which case it should implement a subclass of this which
* contains that state.
*/
public
static
class
SavedState
extends
BaseSavedState
{
int
position
;
Parcelable
adapterState
;
ClassLoader
loader
;
public
SavedState
(
Parcelable
superState
)
{
super
(
superState
);
}
@Override
public
void
writeToParcel
(
Parcel
out
,
int
flags
)
{
super
.
writeToParcel
(
out
,
flags
);
out
.
writeInt
(
position
);
out
.
writeParcelable
(
adapterState
,
flags
);
}
@Override
public
String
toString
()
{
return
"FragmentPager.SavedState{"
+
Integer
.
toHexString
(
System
.
identityHashCode
(
this
))
+
" position="
+
position
+
"}"
;
}
public
static
final
Parcelable
.
Creator
<
SavedState
>
CREATOR
=
ParcelableCompat
.
newCreator
(
new
ParcelableCompatCreatorCallbacks
<
SavedState
>()
{
@Override
public
SavedState
createFromParcel
(
Parcel
in
,
ClassLoader
loader
)
{
return
new
SavedState
(
in
,
loader
);
}
@Override
public
SavedState
[]
newArray
(
int
size
)
{
return
new
SavedState
[
size
];
}
});
SavedState
(
Parcel
in
,
ClassLoader
loader
)
{
super
(
in
);
if
(
loader
==
null
)
{
loader
=
getClass
().
getClassLoader
();
}
position
=
in
.
readInt
();
adapterState
=
in
.
readParcelable
(
loader
);
this
.
loader
=
loader
;
}
}
@Override
public
Parcelable
onSaveInstanceState
()
{
Parcelable
superState
=
super
.
onSaveInstanceState
();
SavedState
ss
=
new
SavedState
(
superState
);
ss
.
position
=
mCurItem
;
if
(
mAdapter
!=
null
)
{
ss
.
adapterState
=
mAdapter
.
saveState
();
}
return
ss
;
}
@Override
public
void
onRestoreInstanceState
(
Parcelable
state
)
{
if
(!(
state
instanceof
SavedState
))
{
super
.
onRestoreInstanceState
(
state
);
return
;
}
SavedState
ss
=
(
SavedState
)
state
;
super
.
onRestoreInstanceState
(
ss
.
getSuperState
());
if
(
mAdapter
!=
null
)
{
mAdapter
.
restoreState
(
ss
.
adapterState
,
ss
.
loader
);
setCurrentItemInternal
(
ss
.
position
,
false
,
true
);
}
else
{
mRestoredCurItem
=
ss
.
position
;
mRestoredAdapterState
=
ss
.
adapterState
;
mRestoredClassLoader
=
ss
.
loader
;
}
}
@Override
public
void
addView
(
View
child
,
int
index
,
ViewGroup
.
LayoutParams
params
)
{
if
(!
checkLayoutParams
(
params
))
{
params
=
generateLayoutParams
(
params
);
}
final
LayoutParams
lp
=
(
LayoutParams
)
params
;
lp
.
isDecor
|=
child
instanceof
Decor
;
if
(
mInLayout
)
{
if
(
lp
!=
null
&&
lp
.
isDecor
)
{
throw
new
IllegalStateException
(
"Cannot add pager decor view during layout"
);
}
addViewInLayout
(
child
,
index
,
params
);
child
.
measure
(
mChildWidthMeasureSpec
,
mChildHeightMeasureSpec
);
}
else
{
super
.
addView
(
child
,
index
,
params
);
}
if
(
USE_CACHE
)
{
if
(
child
.
getVisibility
()
!=
GONE
)
{
child
.
setDrawingCacheEnabled
(
mScrollingCacheEnabled
);
}
else
{
child
.
setDrawingCacheEnabled
(
false
);
}
}
}
ItemInfo
infoForChild
(
View
child
)
{
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
ItemInfo
ii
=
mItems
.
get
(
i
);
if
(
mAdapter
.
isViewFromObject
(
child
,
ii
.
object
))
{
return
ii
;
}
}
return
null
;
}
ItemInfo
infoForAnyChild
(
View
child
)
{
ViewParent
parent
;
while
((
parent
=
child
.
getParent
())
!=
this
)
{
if
(
parent
==
null
||
!(
parent
instanceof
View
))
{
return
null
;
}
child
=
(
View
)
parent
;
}
return
infoForChild
(
child
);
}
@Override
protected
void
onAttachedToWindow
()
{
super
.
onAttachedToWindow
();
mFirstLayout
=
true
;
}
@Override
protected
void
onMeasure
(
int
widthMeasureSpec
,
int
heightMeasureSpec
)
{
// For simple implementation, or internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension
(
getDefaultSize
(
0
,
widthMeasureSpec
),
getDefaultSize
(
0
,
heightMeasureSpec
));
// Children are just made to fill our space.
int
childWidthSize
=
getMeasuredWidth
()
-
getPaddingLeft
()
-
getPaddingRight
();
int
childHeightSize
=
getMeasuredHeight
()
-
getPaddingTop
()
-
getPaddingBottom
();
/*
* Make sure all children have been properly measured. Decor views first.
* Right now we cheat and make this less complicated by assuming decor
* views won't intersect. We will pin to edges based on gravity.
*/
int
size
=
getChildCount
();
for
(
int
i
=
0
;
i
<
size
;
++
i
)
{
final
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
!=
GONE
)
{
final
LayoutParams
lp
=
(
LayoutParams
)
child
.
getLayoutParams
();
if
(
lp
!=
null
&&
lp
.
isDecor
)
{
final
int
hgrav
=
lp
.
gravity
&
Gravity
.
HORIZONTAL_GRAVITY_MASK
;
final
int
vgrav
=
lp
.
gravity
&
Gravity
.
VERTICAL_GRAVITY_MASK
;
Log
.
d
(
TAG
,
"gravity: "
+
lp
.
gravity
+
" hgrav: "
+
hgrav
+
" vgrav: "
+
vgrav
);
int
widthMode
=
MeasureSpec
.
AT_MOST
;
int
heightMode
=
MeasureSpec
.
AT_MOST
;
boolean
consumeVertical
=
vgrav
==
Gravity
.
TOP
||
vgrav
==
Gravity
.
BOTTOM
;
boolean
consumeHorizontal
=
hgrav
==
Gravity
.
LEFT
||
hgrav
==
Gravity
.
RIGHT
;
if
(
consumeVertical
)
{
widthMode
=
MeasureSpec
.
EXACTLY
;
}
else
if
(
consumeHorizontal
)
{
heightMode
=
MeasureSpec
.
EXACTLY
;
}
final
int
widthSpec
=
MeasureSpec
.
makeMeasureSpec
(
childWidthSize
,
widthMode
);
final
int
heightSpec
=
MeasureSpec
.
makeMeasureSpec
(
childHeightSize
,
heightMode
);
child
.
measure
(
widthSpec
,
heightSpec
);
if
(
consumeVertical
)
{
childHeightSize
-=
child
.
getMeasuredHeight
();
}
else
if
(
consumeHorizontal
)
{
childWidthSize
-=
child
.
getMeasuredWidth
();
}
}
}
}
mChildWidthMeasureSpec
=
MeasureSpec
.
makeMeasureSpec
(
childWidthSize
,
MeasureSpec
.
EXACTLY
);
mChildHeightMeasureSpec
=
MeasureSpec
.
makeMeasureSpec
(
childHeightSize
,
MeasureSpec
.
EXACTLY
);
// Make sure we have created all fragments that we need to have shown.
mInLayout
=
true
;
populate
();
mInLayout
=
false
;
// Page views next.
size
=
getChildCount
();
for
(
int
i
=
0
;
i
<
size
;
++
i
)
{
final
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
!=
GONE
)
{
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Measuring #"
+
i
+
" "
+
child
+
": "
+
mChildWidthMeasureSpec
);
final
LayoutParams
lp
=
(
LayoutParams
)
child
.
getLayoutParams
();
if
(
lp
==
null
||
!
lp
.
isDecor
)
{
child
.
measure
(
mChildWidthMeasureSpec
,
mChildHeightMeasureSpec
);
}
}
}
}
@Override
protected
void
onSizeChanged
(
int
w
,
int
h
,
int
oldw
,
int
oldh
)
{
super
.
onSizeChanged
(
w
,
h
,
oldw
,
oldh
);
// Make sure scroll position is set correctly.
if
(
w
!=
oldw
)
{
recomputeScrollPosition
(
w
,
oldw
,
mPageMargin
,
mPageMargin
);
}
}
private
void
recomputeScrollPosition
(
int
width
,
int
oldWidth
,
int
margin
,
int
oldMargin
)
{
final
int
widthWithMargin
=
width
+
margin
;
if
(
oldWidth
>
0
)
{
final
int
oldScrollPos
=
getScrollX
();
final
int
oldwwm
=
oldWidth
+
oldMargin
;
final
int
oldScrollItem
=
oldScrollPos
/
oldwwm
;
final
float
scrollOffset
=
(
float
)
(
oldScrollPos
%
oldwwm
)
/
oldwwm
;
final
int
scrollPos
=
(
int
)
((
oldScrollItem
+
scrollOffset
)
*
widthWithMargin
);
scrollTo
(
scrollPos
,
getScrollY
());
if
(!
mScroller
.
isFinished
())
{
// We now return to your regularly scheduled scroll, already in progress.
final
int
newDuration
=
mScroller
.
getDuration
()
-
mScroller
.
timePassed
();
mScroller
.
startScroll
(
scrollPos
,
0
,
mCurItem
*
widthWithMargin
,
0
,
newDuration
);
}
}
else
{
int
scrollPos
=
mCurItem
*
widthWithMargin
;
if
(
scrollPos
!=
getScrollX
())
{
completeScroll
();
scrollTo
(
scrollPos
,
getScrollY
());
}
}
}
@Override
protected
void
onLayout
(
boolean
changed
,
int
l
,
int
t
,
int
r
,
int
b
)
{
mInLayout
=
true
;
populate
();
mInLayout
=
false
;
final
int
count
=
getChildCount
();
int
width
=
r
-
l
;
int
height
=
b
-
t
;
int
paddingLeft
=
getPaddingLeft
();
int
paddingTop
=
getPaddingTop
();
int
paddingRight
=
getPaddingRight
();
int
paddingBottom
=
getPaddingBottom
();
final
int
scrollX
=
getScrollX
();
int
decorCount
=
0
;
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
final
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
!=
GONE
)
{
final
LayoutParams
lp
=
(
LayoutParams
)
child
.
getLayoutParams
();
ItemInfo
ii
;
int
childLeft
=
0
;
int
childTop
=
0
;
if
(
lp
.
isDecor
)
{
final
int
hgrav
=
lp
.
gravity
&
Gravity
.
HORIZONTAL_GRAVITY_MASK
;
final
int
vgrav
=
lp
.
gravity
&
Gravity
.
VERTICAL_GRAVITY_MASK
;
switch
(
hgrav
)
{
default
:
childLeft
=
paddingLeft
;
break
;
case
Gravity
.
LEFT
:
childLeft
=
paddingLeft
;
paddingLeft
+=
child
.
getMeasuredWidth
();
break
;
case
Gravity
.
CENTER_HORIZONTAL
:
childLeft
=
Math
.
max
((
width
-
child
.
getMeasuredWidth
())
/
2
,
paddingLeft
);
break
;
case
Gravity
.
RIGHT
:
childLeft
=
width
-
paddingRight
-
child
.
getMeasuredWidth
();
paddingRight
+=
child
.
getMeasuredWidth
();
break
;
}
switch
(
vgrav
)
{
default
:
childTop
=
paddingTop
;
break
;
case
Gravity
.
TOP
:
childTop
=
paddingTop
;
paddingTop
+=
child
.
getMeasuredHeight
();
break
;
case
Gravity
.
CENTER_VERTICAL
:
childTop
=
Math
.
max
((
height
-
child
.
getMeasuredHeight
())
/
2
,
paddingTop
);
break
;
case
Gravity
.
BOTTOM
:
childTop
=
height
-
paddingBottom
-
child
.
getMeasuredHeight
();
paddingBottom
+=
child
.
getMeasuredHeight
();
break
;
}
childLeft
+=
scrollX
;
decorCount
++;
child
.
layout
(
childLeft
,
childTop
,
childLeft
+
child
.
getMeasuredWidth
(),
childTop
+
child
.
getMeasuredHeight
());
}
else
if
((
ii
=
infoForChild
(
child
))
!=
null
)
{
int
loff
=
(
width
+
mPageMargin
)
*
ii
.
position
;
childLeft
=
paddingLeft
+
loff
;
childTop
=
paddingTop
;
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Positioning #"
+
i
+
" "
+
child
+
" f="
+
ii
.
object
+
":"
+
childLeft
+
","
+
childTop
+
" "
+
child
.
getMeasuredWidth
()
+
"x"
+
child
.
getMeasuredHeight
());
child
.
layout
(
childLeft
,
childTop
,
childLeft
+
child
.
getMeasuredWidth
(),
childTop
+
child
.
getMeasuredHeight
());
}
}
}
mTopPageBounds
=
paddingTop
;
mBottomPageBounds
=
height
-
paddingBottom
;
mDecorChildCount
=
decorCount
;
mFirstLayout
=
false
;
}
@Override
public
void
computeScroll
()
{
if
(
DEBUG
)
Log
.
i
(
TAG
,
"computeScroll: finished="
+
mScroller
.
isFinished
());
if
(!
mScroller
.
isFinished
())
{
if
(
mScroller
.
computeScrollOffset
())
{
if
(
DEBUG
)
Log
.
i
(
TAG
,
"computeScroll: still scrolling"
);
int
oldX
=
getScrollX
();
int
oldY
=
getScrollY
();
int
x
=
mScroller
.
getCurrX
();
int
y
=
mScroller
.
getCurrY
();
if
(
oldX
!=
x
||
oldY
!=
y
)
{
scrollTo
(
x
,
y
);
pageScrolled
(
x
);
}
// Keep on drawing until the animation has finished.
invalidate
();
return
;
}
}
// Done with scroll, clean up state.
completeScroll
();
}
private
void
pageScrolled
(
int
xpos
)
{
final
int
widthWithMargin
=
getWidth
()
+
mPageMargin
;
final
int
position
=
xpos
/
widthWithMargin
;
final
int
offsetPixels
=
xpos
%
widthWithMargin
;
final
float
offset
=
(
float
)
offsetPixels
/
widthWithMargin
;
mCalledSuper
=
false
;
onPageScrolled
(
position
,
offset
,
offsetPixels
);
if
(!
mCalledSuper
)
{
throw
new
IllegalStateException
(
"onPageScrolled did not call superclass implementation"
);
}
}
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
* If you override this method you must call through to the superclass implementation
* (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
* returns.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param offset Value from [0, 1) indicating the offset from the page at position.
* @param offsetPixels Value in pixels indicating the offset from position.
*/
protected
void
onPageScrolled
(
int
position
,
float
offset
,
int
offsetPixels
)
{
// Offset any decor views if needed - keep them on-screen at all times.
if
(
mDecorChildCount
>
0
)
{
final
int
scrollX
=
getScrollX
();
int
paddingLeft
=
getPaddingLeft
();
int
paddingRight
=
getPaddingRight
();
final
int
width
=
getWidth
();
final
int
childCount
=
getChildCount
();
for
(
int
i
=
0
;
i
<
childCount
;
i
++)
{
final
View
child
=
getChildAt
(
i
);
final
LayoutParams
lp
=
(
LayoutParams
)
child
.
getLayoutParams
();
if
(!
lp
.
isDecor
)
continue
;
final
int
hgrav
=
lp
.
gravity
&
Gravity
.
HORIZONTAL_GRAVITY_MASK
;
int
childLeft
=
0
;
switch
(
hgrav
)
{
default
:
childLeft
=
paddingLeft
;
break
;
case
Gravity
.
LEFT
:
childLeft
=
paddingLeft
;
paddingLeft
+=
child
.
getWidth
();
break
;
case
Gravity
.
CENTER_HORIZONTAL
:
childLeft
=
Math
.
max
((
width
-
child
.
getMeasuredWidth
())
/
2
,
paddingLeft
);
break
;
case
Gravity
.
RIGHT
:
childLeft
=
width
-
paddingRight
-
child
.
getMeasuredWidth
();
paddingRight
+=
child
.
getMeasuredWidth
();
break
;
}
childLeft
+=
scrollX
;
final
int
childOffset
=
childLeft
-
child
.
getLeft
();
if
(
childOffset
!=
0
)
{
child
.
offsetLeftAndRight
(
childOffset
);
}
}
}
if
(
mOnPageChangeListener
!=
null
)
{
mOnPageChangeListener
.
onPageScrolled
(
position
,
offset
,
offsetPixels
);
}
if
(
mInternalPageChangeListener
!=
null
)
{
mInternalPageChangeListener
.
onPageScrolled
(
position
,
offset
,
offsetPixels
);
}
mCalledSuper
=
true
;
}
private
void
completeScroll
()
{
boolean
needPopulate
=
mScrolling
;
if
(
needPopulate
)
{
// Done with scroll, no longer want to cache view drawing.
setScrollingCacheEnabled
(
false
);
mScroller
.
abortAnimation
();
int
oldX
=
getScrollX
();
int
oldY
=
getScrollY
();
int
x
=
mScroller
.
getCurrX
();
int
y
=
mScroller
.
getCurrY
();
if
(
oldX
!=
x
||
oldY
!=
y
)
{
scrollTo
(
x
,
y
);
}
setScrollState
(
SCROLL_STATE_IDLE
);
}
mPopulatePending
=
false
;
mScrolling
=
false
;
for
(
int
i
=
0
;
i
<
mItems
.
size
();
i
++)
{
ItemInfo
ii
=
mItems
.
get
(
i
);
if
(
ii
.
scrolling
)
{
needPopulate
=
true
;
ii
.
scrolling
=
false
;
}
}
if
(
needPopulate
)
{
populate
();
}
}
@Override
public
boolean
onInterceptTouchEvent
(
MotionEvent
ev
)
{
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/
final
int
action
=
ev
.
getAction
()
&
MotionEventCompat
.
ACTION_MASK
;
// Always take care of the touch gesture being complete.
if
(
action
==
MotionEvent
.
ACTION_CANCEL
||
action
==
MotionEvent
.
ACTION_UP
)
{
// Release the drag.
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Intercept done!"
);
mIsBeingDragged
=
false
;
mIsUnableToDrag
=
false
;
mActivePointerId
=
INVALID_POINTER
;
if
(
mVelocityTracker
!=
null
)
{
mVelocityTracker
.
recycle
();
mVelocityTracker
=
null
;
}
return
false
;
}
// Nothing more to do here if we have decided whether or not we
// are dragging.
if
(
action
!=
MotionEvent
.
ACTION_DOWN
)
{
if
(
mIsBeingDragged
)
{
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Intercept returning true!"
);
return
true
;
}
if
(
mIsUnableToDrag
)
{
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Intercept returning false!"
);
return
false
;
}
}
switch
(
action
)
{
case
MotionEvent
.
ACTION_MOVE
:
{
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final
int
activePointerId
=
mActivePointerId
;
if
(
activePointerId
==
INVALID_POINTER
)
{
// If we don't have a valid id, the touch down wasn't on content.
break
;
}
final
int
pointerIndex
=
MotionEventCompat
.
findPointerIndex
(
ev
,
activePointerId
);
final
float
x
=
MotionEventCompat
.
getX
(
ev
,
pointerIndex
);
final
float
dx
=
x
-
mLastMotionX
;
final
float
xDiff
=
Math
.
abs
(
dx
);
final
float
y
=
MotionEventCompat
.
getY
(
ev
,
pointerIndex
);
final
float
yDiff
=
Math
.
abs
(
y
-
mLastMotionY
);
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Moved x to "
+
x
+
","
+
y
+
" diff="
+
xDiff
+
","
+
yDiff
);
if
(
canScroll
(
this
,
false
,
(
int
)
dx
,
(
int
)
x
,
(
int
)
y
))
{
// Nested view has scrollable area under this point. Let it be handled there.
mInitialMotionX
=
mLastMotionX
=
x
;
mLastMotionY
=
y
;
return
false
;
}
if
(
xDiff
>
mTouchSlop
&&
xDiff
>
yDiff
)
{
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Starting drag!"
);
mIsBeingDragged
=
true
;
setScrollState
(
SCROLL_STATE_DRAGGING
);
mLastMotionX
=
x
;
setScrollingCacheEnabled
(
true
);
}
else
{
if
(
yDiff
>
mTouchSlop
)
{
// The finger has moved enough in the vertical
// direction to be counted as a drag... abort
// any attempt to drag horizontally, to work correctly
// with children that have scrolling containers.
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Starting unable to drag!"
);
mIsUnableToDrag
=
true
;
}
}
break
;
}
case
MotionEvent
.
ACTION_DOWN
:
{
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX
=
mInitialMotionX
=
ev
.
getX
();
mLastMotionY
=
ev
.
getY
();
mActivePointerId
=
MotionEventCompat
.
getPointerId
(
ev
,
0
);
if
(
mScrollState
==
SCROLL_STATE_SETTLING
)
{
// Let the user 'catch' the pager as it animates.
mIsBeingDragged
=
true
;
mIsUnableToDrag
=
false
;
setScrollState
(
SCROLL_STATE_DRAGGING
);
}
else
{
completeScroll
();
mIsBeingDragged
=
false
;
mIsUnableToDrag
=
false
;
}
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Down at "
+
mLastMotionX
+
","
+
mLastMotionY
+
" mIsBeingDragged="
+
mIsBeingDragged
+
"mIsUnableToDrag="
+
mIsUnableToDrag
);
break
;
}
case
MotionEventCompat
.
ACTION_POINTER_UP
:
onSecondaryPointerUp
(
ev
);
break
;
}
if
(!
mIsBeingDragged
)
{
// Track the velocity as long as we aren't dragging.
// Once we start a real drag we will track in onTouchEvent.
if
(
mVelocityTracker
==
null
)
{
mVelocityTracker
=
VelocityTracker
.
obtain
();
}
mVelocityTracker
.
addMovement
(
ev
);
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return
mIsBeingDragged
;
}
@Override
public
boolean
onTouchEvent
(
MotionEvent
ev
)
{
if
(
mFakeDragging
)
{
// A fake drag is in progress already, ignore this real one
// but still eat the touch events.
// (It is likely that the user is multi-touching the screen.)
return
true
;
}
if
(
ev
.
getAction
()
==
MotionEvent
.
ACTION_DOWN
&&
ev
.
getEdgeFlags
()
!=
0
)
{
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return
false
;
}
if
(
mAdapter
==
null
||
mAdapter
.
getCount
()
==
0
)
{
// Nothing to present or scroll; nothing to touch.
return
false
;
}
if
(
mVelocityTracker
==
null
)
{
mVelocityTracker
=
VelocityTracker
.
obtain
();
}
mVelocityTracker
.
addMovement
(
ev
);
final
int
action
=
ev
.
getAction
();
boolean
needsInvalidate
=
false
;
switch
(
action
&
MotionEventCompat
.
ACTION_MASK
)
{
case
MotionEvent
.
ACTION_DOWN
:
{
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
completeScroll
();
// Remember where the motion event started
mLastMotionX
=
mInitialMotionX
=
ev
.
getX
();
mActivePointerId
=
MotionEventCompat
.
getPointerId
(
ev
,
0
);
break
;
}
case
MotionEvent
.
ACTION_MOVE
:
if
(!
mIsBeingDragged
)
{
final
int
pointerIndex
=
MotionEventCompat
.
findPointerIndex
(
ev
,
mActivePointerId
);
final
float
x
=
MotionEventCompat
.
getX
(
ev
,
pointerIndex
);
final
float
xDiff
=
Math
.
abs
(
x
-
mLastMotionX
);
final
float
y
=
MotionEventCompat
.
getY
(
ev
,
pointerIndex
);
final
float
yDiff
=
Math
.
abs
(
y
-
mLastMotionY
);
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Moved x to "
+
x
+
","
+
y
+
" diff="
+
xDiff
+
","
+
yDiff
);
if
(
xDiff
>
mTouchSlop
&&
xDiff
>
yDiff
)
{
if
(
DEBUG
)
Log
.
v
(
TAG
,
"Starting drag!"
);
mIsBeingDragged
=
true
;
mLastMotionX
=
x
;
setScrollState
(
SCROLL_STATE_DRAGGING
);
setScrollingCacheEnabled
(
true
);
}
}
if
(
mIsBeingDragged
)
{
// Scroll to follow the motion event
final
int
activePointerIndex
=
MotionEventCompat
.
findPointerIndex
(
ev
,
mActivePointerId
);
final
float
x
=
MotionEventCompat
.
getX
(
ev
,
activePointerIndex
);
final
float
deltaX
=
mLastMotionX
-
x
;
mLastMotionX
=
x
;
float
oldScrollX
=
getScrollX
();
float
scrollX
=
oldScrollX
+
deltaX
;
final
int
width
=
getWidth
();
final
int
widthWithMargin
=
width
+
mPageMargin
;
final
int
lastItemIndex
=
mAdapter
.
getCount
()
-
1
;
final
float
leftBound
=
Math
.
max
(
0
,
(
mCurItem
-
1
)
*
widthWithMargin
);
final
float
rightBound
=
Math
.
min
(
mCurItem
+
1
,
lastItemIndex
)
*
widthWithMargin
;
if
(
scrollX
<
leftBound
)
{
if
(
leftBound
==
0
)
{
float
over
=
-
scrollX
;
needsInvalidate
=
mLeftEdge
.
onPull
(
over
/
width
);
}
scrollX
=
leftBound
;
}
else
if
(
scrollX
>
rightBound
)
{
if
(
rightBound
==
lastItemIndex
*
widthWithMargin
)
{
float
over
=
scrollX
-
rightBound
;
needsInvalidate
=
mRightEdge
.
onPull
(
over
/
width
);
}
scrollX
=
rightBound
;
}
// Don't lose the rounded component
mLastMotionX
+=
scrollX
-
(
int
)
scrollX
;
scrollTo
((
int
)
scrollX
,
getScrollY
());
pageScrolled
((
int
)
scrollX
);
}
break
;
case
MotionEvent
.
ACTION_UP
:
if
(
mIsBeingDragged
)
{
final
VelocityTracker
velocityTracker
=
mVelocityTracker
;
velocityTracker
.
computeCurrentVelocity
(
1000
,
mMaximumVelocity
);
int
initialVelocity
=
(
int
)
VelocityTrackerCompat
.
getXVelocity
(
velocityTracker
,
mActivePointerId
);
mPopulatePending
=
true
;
final
int
widthWithMargin
=
getWidth
()
+
mPageMargin
;
final
int
scrollX
=
getScrollX
();
final
int
currentPage
=
scrollX
/
widthWithMargin
;
final
float
pageOffset
=
(
float
)
(
scrollX
%
widthWithMargin
)
/
widthWithMargin
;
final
int
activePointerIndex
=
MotionEventCompat
.
findPointerIndex
(
ev
,
mActivePointerId
);
final
float
x
=
MotionEventCompat
.
getX
(
ev
,
activePointerIndex
);
final
int
totalDelta
=
(
int
)
(
x
-
mInitialMotionX
);
int
nextPage
=
determineTargetPage
(
currentPage
,
pageOffset
,
initialVelocity
,
totalDelta
);
setCurrentItemInternal
(
nextPage
,
true
,
true
,
initialVelocity
);
mActivePointerId
=
INVALID_POINTER
;
endDrag
();
needsInvalidate
=
mLeftEdge
.
onRelease
()
|
mRightEdge
.
onRelease
();
}
break
;
case
MotionEvent
.
ACTION_CANCEL
:
if
(
mIsBeingDragged
)
{
setCurrentItemInternal
(
mCurItem
,
true
,
true
);
mActivePointerId
=
INVALID_POINTER
;
endDrag
();
needsInvalidate
=
mLeftEdge
.
onRelease
()
|
mRightEdge
.
onRelease
();
}
break
;
case
MotionEventCompat
.
ACTION_POINTER_DOWN
:
{
final
int
index
=
MotionEventCompat
.
getActionIndex
(
ev
);
final
float
x
=
MotionEventCompat
.
getX
(
ev
,
index
);
mLastMotionX
=
x
;
mActivePointerId
=
MotionEventCompat
.
getPointerId
(
ev
,
index
);
break
;
}
case
MotionEventCompat
.
ACTION_POINTER_UP
:
onSecondaryPointerUp
(
ev
);
mLastMotionX
=
MotionEventCompat
.
getX
(
ev
,
MotionEventCompat
.
findPointerIndex
(
ev
,
mActivePointerId
));
break
;
}
if
(
needsInvalidate
)
{
invalidate
();
}
return
true
;
}
private
int
determineTargetPage
(
int
currentPage
,
float
pageOffset
,
int
velocity
,
int
deltaX
)
{
int
targetPage
;
if
(
Math
.
abs
(
deltaX
)
>
mFlingDistance
&&
Math
.
abs
(
velocity
)
>
mMinimumVelocity
)
{
targetPage
=
velocity
>
0
?
currentPage
:
currentPage
+
1
;
}
else
{
targetPage
=
(
int
)
(
currentPage
+
pageOffset
+
0.5f
);
}
return
targetPage
;
}
@Override
public
void
draw
(
Canvas
canvas
)
{
super
.
draw
(
canvas
);
boolean
needsInvalidate
=
false
;
final
int
overScrollMode
=
ViewCompat
.
getOverScrollMode
(
this
);
if
(
overScrollMode
==
ViewCompat
.
OVER_SCROLL_ALWAYS
||
(
overScrollMode
==
ViewCompat
.
OVER_SCROLL_IF_CONTENT_SCROLLS
&&
mAdapter
!=
null
&&
mAdapter
.
getCount
()
>
1
))
{
if
(!
mLeftEdge
.
isFinished
())
{
final
int
restoreCount
=
canvas
.
save
();
final
int
height
=
getHeight
()
-
getPaddingTop
()
-
getPaddingBottom
();
canvas
.
rotate
(
270
);
canvas
.
translate
(-
height
+
getPaddingTop
(),
0
);
mLeftEdge
.
setSize
(
height
,
getWidth
());
needsInvalidate
|=
mLeftEdge
.
draw
(
canvas
);
canvas
.
restoreToCount
(
restoreCount
);
}
if
(!
mRightEdge
.
isFinished
())
{
final
int
restoreCount
=
canvas
.
save
();
final
int
width
=
getWidth
();
final
int
height
=
getHeight
()
-
getPaddingTop
()
-
getPaddingBottom
();
final
int
itemCount
=
mAdapter
!=
null
?
mAdapter
.
getCount
()
:
1
;
canvas
.
rotate
(
90
);
canvas
.
translate
(-
getPaddingTop
(),
-
itemCount
*
(
width
+
mPageMargin
)
+
mPageMargin
);
mRightEdge
.
setSize
(
height
,
width
);
needsInvalidate
|=
mRightEdge
.
draw
(
canvas
);
canvas
.
restoreToCount
(
restoreCount
);
}
}
else
{
mLeftEdge
.
finish
();
mRightEdge
.
finish
();
}
if
(
needsInvalidate
)
{
// Keep animating
invalidate
();
}
}
@Override
protected
void
onDraw
(
Canvas
canvas
)
{
super
.
onDraw
(
canvas
);
// Draw the margin drawable if needed.
if
(
mPageMargin
>
0
&&
mMarginDrawable
!=
null
)
{
final
int
scrollX
=
getScrollX
();
final
int
width
=
getWidth
();
final
int
offset
=
scrollX
%
(
width
+
mPageMargin
);
if
(
offset
!=
0
)
{
// Pages fit completely when settled; we only need to draw when in between
final
int
left
=
scrollX
-
offset
+
width
;
mMarginDrawable
.
setBounds
(
left
,
mTopPageBounds
,
left
+
mPageMargin
,
mBottomPageBounds
);
mMarginDrawable
.
draw
(
canvas
);
}
}
}
/**
* Start a fake drag of the pager.
*
* <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
* with the touch scrolling of another view, while still letting the ViewPager
* control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
* Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
* {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
*
* <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
* is already in progress, this method will return false.
*
* @return true if the fake drag began successfully, false if it could not be started.
*
* @see #fakeDragBy(float)
* @see #endFakeDrag()
*/
public
boolean
beginFakeDrag
()
{
if
(
mIsBeingDragged
)
{
return
false
;
}
mFakeDragging
=
true
;
setScrollState
(
SCROLL_STATE_DRAGGING
);
mInitialMotionX
=
mLastMotionX
=
0
;
if
(
mVelocityTracker
==
null
)
{
mVelocityTracker
=
VelocityTracker
.
obtain
();
}
else
{
mVelocityTracker
.
clear
();
}
final
long
time
=
SystemClock
.
uptimeMillis
();
final
MotionEvent
ev
=
MotionEvent
.
obtain
(
time
,
time
,
MotionEvent
.
ACTION_DOWN
,
0
,
0
,
0
);
mVelocityTracker
.
addMovement
(
ev
);
ev
.
recycle
();
mFakeDragBeginTime
=
time
;
return
true
;
}
/**
* End a fake drag of the pager.
*
* @see #beginFakeDrag()
* @see #fakeDragBy(float)
*/
public
void
endFakeDrag
()
{
if
(!
mFakeDragging
)
{
throw
new
IllegalStateException
(
"No fake drag in progress. Call beginFakeDrag first."
);
}
final
VelocityTracker
velocityTracker
=
mVelocityTracker
;
velocityTracker
.
computeCurrentVelocity
(
1000
,
mMaximumVelocity
);
int
initialVelocity
=
(
int
)
VelocityTrackerCompat
.
getYVelocity
(
velocityTracker
,
mActivePointerId
);
mPopulatePending
=
true
;
final
int
totalDelta
=
(
int
)
(
mLastMotionX
-
mInitialMotionX
);
final
int
scrollX
=
getScrollX
();
final
int
widthWithMargin
=
getWidth
()
+
mPageMargin
;
final
int
currentPage
=
scrollX
/
widthWithMargin
;
final
float
pageOffset
=
(
float
)
(
scrollX
%
widthWithMargin
)
/
widthWithMargin
;
int
nextPage
=
determineTargetPage
(
currentPage
,
pageOffset
,
initialVelocity
,
totalDelta
);
setCurrentItemInternal
(
nextPage
,
true
,
true
,
initialVelocity
);
endDrag
();
mFakeDragging
=
false
;
}
/**
* Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
*
* @param xOffset Offset in pixels to drag by.
* @see #beginFakeDrag()
* @see #endFakeDrag()
*/
public
void
fakeDragBy
(
float
xOffset
)
{
if
(!
mFakeDragging
)
{
throw
new
IllegalStateException
(
"No fake drag in progress. Call beginFakeDrag first."
);
}
mLastMotionX
+=
xOffset
;
float
scrollX
=
getScrollX
()
-
xOffset
;
final
int
width
=
getWidth
();
final
int
widthWithMargin
=
width
+
mPageMargin
;
final
float
leftBound
=
Math
.
max
(
0
,
(
mCurItem
-
1
)
*
widthWithMargin
);
final
float
rightBound
=
Math
.
min
(
mCurItem
+
1
,
mAdapter
.
getCount
()
-
1
)
*
widthWithMargin
;
if
(
scrollX
<
leftBound
)
{
scrollX
=
leftBound
;
}
else
if
(
scrollX
>
rightBound
)
{
scrollX
=
rightBound
;
}
// Don't lose the rounded component
mLastMotionX
+=
scrollX
-
(
int
)
scrollX
;
scrollTo
((
int
)
scrollX
,
getScrollY
());
pageScrolled
((
int
)
scrollX
);
// Synthesize an event for the VelocityTracker.
final
long
time
=
SystemClock
.
uptimeMillis
();
final
MotionEvent
ev
=
MotionEvent
.
obtain
(
mFakeDragBeginTime
,
time
,
MotionEvent
.
ACTION_MOVE
,
mLastMotionX
,
0
,
0
);
mVelocityTracker
.
addMovement
(
ev
);
ev
.
recycle
();
}
/**
* Returns true if a fake drag is in progress.
*
* @return true if currently in a fake drag, false otherwise.
*
* @see #beginFakeDrag()
* @see #fakeDragBy(float)
* @see #endFakeDrag()
*/
public
boolean
isFakeDragging
()
{
return
mFakeDragging
;
}
private
void
onSecondaryPointerUp
(
MotionEvent
ev
)
{
final
int
pointerIndex
=
MotionEventCompat
.
getActionIndex
(
ev
);
final
int
pointerId
=
MotionEventCompat
.
getPointerId
(
ev
,
pointerIndex
);
if
(
pointerId
==
mActivePointerId
)
{
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final
int
newPointerIndex
=
pointerIndex
==
0
?
1
:
0
;
mLastMotionX
=
MotionEventCompat
.
getX
(
ev
,
newPointerIndex
);
mActivePointerId
=
MotionEventCompat
.
getPointerId
(
ev
,
newPointerIndex
);
if
(
mVelocityTracker
!=
null
)
{
mVelocityTracker
.
clear
();
}
}
}
private
void
endDrag
()
{
mIsBeingDragged
=
false
;
mIsUnableToDrag
=
false
;
if
(
mVelocityTracker
!=
null
)
{
mVelocityTracker
.
recycle
();
mVelocityTracker
=
null
;
}
}
private
void
setScrollingCacheEnabled
(
boolean
enabled
)
{
if
(
mScrollingCacheEnabled
!=
enabled
)
{
mScrollingCacheEnabled
=
enabled
;
if
(
USE_CACHE
)
{
final
int
size
=
getChildCount
();
for
(
int
i
=
0
;
i
<
size
;
++
i
)
{
final
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
!=
GONE
)
{
child
.
setDrawingCacheEnabled
(
enabled
);
}
}
}
}
}
/**
* Tests scrollability within child views of v given a delta of dx.
*
* @param v View to test for horizontal scrollability
* @param checkV Whether the view v passed should itself be checked for scrollability (true),
* or just its children (false).
* @param dx Delta scrolled in pixels
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dx.
*/
protected
boolean
canScroll
(
View
v
,
boolean
checkV
,
int
dx
,
int
x
,
int
y
)
{
if
(
v
instanceof
ViewGroup
)
{
final
ViewGroup
group
=
(
ViewGroup
)
v
;
final
int
scrollX
=
v
.
getScrollX
();
final
int
scrollY
=
v
.
getScrollY
();
final
int
count
=
group
.
getChildCount
();
// Count backwards - let topmost views consume scroll distance first.
for
(
int
i
=
count
-
1
;
i
>=
0
;
i
--)
{
// TODO: Add versioned support here for transformed views.
// This will not work for transformed views in Honeycomb+
final
View
child
=
group
.
getChildAt
(
i
);
if
(
x
+
scrollX
>=
child
.
getLeft
()
&&
x
+
scrollX
<
child
.
getRight
()
&&
y
+
scrollY
>=
child
.
getTop
()
&&
y
+
scrollY
<
child
.
getBottom
()
&&
canScroll
(
child
,
true
,
dx
,
x
+
scrollX
-
child
.
getLeft
(),
y
+
scrollY
-
child
.
getTop
()))
{
return
true
;
}
}
}
return
checkV
&&
ViewCompat
.
canScrollHorizontally
(
v
,
-
dx
);
}
@Override
public
boolean
dispatchKeyEvent
(
KeyEvent
event
)
{
// Let the focused view and/or our descendants get the key first
return
super
.
dispatchKeyEvent
(
event
)
||
executeKeyEvent
(
event
);
}
/**
* You can call this function yourself to have the scroll view perform
* scrolling from a key event, just as if the event had been dispatched to
* it by the view hierarchy.
*
* @param event The key event to execute.
* @return Return true if the event was handled, else false.
*/
public
boolean
executeKeyEvent
(
KeyEvent
event
)
{
boolean
handled
=
false
;
if
(
event
.
getAction
()
==
KeyEvent
.
ACTION_DOWN
)
{
switch
(
event
.
getKeyCode
())
{
case
KeyEvent
.
KEYCODE_DPAD_LEFT
:
handled
=
arrowScroll
(
FOCUS_LEFT
);
break
;
case
KeyEvent
.
KEYCODE_DPAD_RIGHT
:
handled
=
arrowScroll
(
FOCUS_RIGHT
);
break
;
case
KeyEvent
.
KEYCODE_TAB
:
if
(
Build
.
VERSION
.
SDK_INT
>=
11
)
{
// The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
// before Android 3.0. Ignore the tab key on those devices.
if
(
KeyEventCompat
.
hasNoModifiers
(
event
))
{
handled
=
arrowScroll
(
FOCUS_FORWARD
);
}
else
if
(
KeyEventCompat
.
hasModifiers
(
event
,
KeyEvent
.
META_SHIFT_ON
))
{
handled
=
arrowScroll
(
FOCUS_BACKWARD
);
}
}
break
;
}
}
return
handled
;
}
public
boolean
arrowScroll
(
int
direction
)
{
View
currentFocused
=
findFocus
();
if
(
currentFocused
==
this
)
currentFocused
=
null
;
boolean
handled
=
false
;
View
nextFocused
=
FocusFinder
.
getInstance
().
findNextFocus
(
this
,
currentFocused
,
direction
);
if
(
nextFocused
!=
null
&&
nextFocused
!=
currentFocused
)
{
if
(
direction
==
View
.
FOCUS_LEFT
)
{
// If there is nothing to the left, or this is causing us to
// jump to the right, then what we really want to do is page left.
if
(
currentFocused
!=
null
&&
nextFocused
.
getLeft
()
>=
currentFocused
.
getLeft
())
{
handled
=
pageLeft
();
}
else
{
handled
=
nextFocused
.
requestFocus
();
}
}
else
if
(
direction
==
View
.
FOCUS_RIGHT
)
{
// If there is nothing to the right, or this is causing us to
// jump to the left, then what we really want to do is page right.
if
(
currentFocused
!=
null
&&
nextFocused
.
getLeft
()
<=
currentFocused
.
getLeft
())
{
handled
=
pageRight
();
}
else
{
handled
=
nextFocused
.
requestFocus
();
}
}
}
else
if
(
direction
==
FOCUS_LEFT
||
direction
==
FOCUS_BACKWARD
)
{
// Trying to move left and nothing there; try to page.
handled
=
pageLeft
();
}
else
if
(
direction
==
FOCUS_RIGHT
||
direction
==
FOCUS_FORWARD
)
{
// Trying to move right and nothing there; try to page.
handled
=
pageRight
();
}
if
(
handled
)
{
playSoundEffect
(
SoundEffectConstants
.
getContantForFocusDirection
(
direction
));
}
return
handled
;
}
boolean
pageLeft
()
{
if
(
mCurItem
>
0
)
{
setCurrentItem
(
mCurItem
-
1
,
true
);
return
true
;
}
return
false
;
}
boolean
pageRight
()
{
if
(
mAdapter
!=
null
&&
mCurItem
<
(
mAdapter
.
getCount
()-
1
))
{
setCurrentItem
(
mCurItem
+
1
,
true
);
return
true
;
}
return
false
;
}
/**
* We only want the current page that is being shown to be focusable.
*/
@Override
public
void
addFocusables
(
ArrayList
<
View
>
views
,
int
direction
,
int
focusableMode
)
{
final
int
focusableCount
=
views
.
size
();
final
int
descendantFocusability
=
getDescendantFocusability
();
if
(
descendantFocusability
!=
FOCUS_BLOCK_DESCENDANTS
)
{
for
(
int
i
=
0
;
i
<
getChildCount
();
i
++)
{
final
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
==
VISIBLE
)
{
ItemInfo
ii
=
infoForChild
(
child
);
if
(
ii
!=
null
&&
ii
.
position
==
mCurItem
)
{
child
.
addFocusables
(
views
,
direction
,
focusableMode
);
}
}
}
}
// we add ourselves (if focusable) in all cases except for when we are
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
if
(
descendantFocusability
!=
FOCUS_AFTER_DESCENDANTS
||
// No focusable descendants
(
focusableCount
==
views
.
size
()))
{
// Note that we can't call the superclass here, because it will
// add all views in. So we need to do the same thing View does.
if
(!
isFocusable
())
{
return
;
}
if
((
focusableMode
&
FOCUSABLES_TOUCH_MODE
)
==
FOCUSABLES_TOUCH_MODE
&&
isInTouchMode
()
&&
!
isFocusableInTouchMode
())
{
return
;
}
if
(
views
!=
null
)
{
views
.
add
(
this
);
}
}
}
/**
* We only want the current page that is being shown to be touchable.
*/
@Override
public
void
addTouchables
(
ArrayList
<
View
>
views
)
{
// Note that we don't call super.addTouchables(), which means that
// we don't call View.addTouchables(). This is okay because a ViewPager
// is itself not touchable.
for
(
int
i
=
0
;
i
<
getChildCount
();
i
++)
{
final
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
==
VISIBLE
)
{
ItemInfo
ii
=
infoForChild
(
child
);
if
(
ii
!=
null
&&
ii
.
position
==
mCurItem
)
{
child
.
addTouchables
(
views
);
}
}
}
}
/**
* We only want the current page that is being shown to be focusable.
*/
@Override
protected
boolean
onRequestFocusInDescendants
(
int
direction
,
Rect
previouslyFocusedRect
)
{
int
index
;
int
increment
;
int
end
;
int
count
=
getChildCount
();
if
((
direction
&
FOCUS_FORWARD
)
!=
0
)
{
index
=
0
;
increment
=
1
;
end
=
count
;
}
else
{
index
=
count
-
1
;
increment
=
-
1
;
end
=
-
1
;
}
for
(
int
i
=
index
;
i
!=
end
;
i
+=
increment
)
{
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
==
VISIBLE
)
{
ItemInfo
ii
=
infoForChild
(
child
);
if
(
ii
!=
null
&&
ii
.
position
==
mCurItem
)
{
if
(
child
.
requestFocus
(
direction
,
previouslyFocusedRect
))
{
return
true
;
}
}
}
}
return
false
;
}
@Override
public
boolean
dispatchPopulateAccessibilityEvent
(
AccessibilityEvent
event
)
{
// ViewPagers should only report accessibility info for the current page,
// otherwise things get very confusing.
// TODO: Should this note something about the paging container?
final
int
childCount
=
getChildCount
();
for
(
int
i
=
0
;
i
<
childCount
;
i
++)
{
final
View
child
=
getChildAt
(
i
);
if
(
child
.
getVisibility
()
==
VISIBLE
)
{
final
ItemInfo
ii
=
infoForChild
(
child
);
if
(
ii
!=
null
&&
ii
.
position
==
mCurItem
&&
child
.
dispatchPopulateAccessibilityEvent
(
event
))
{
return
true
;
}
}
}
return
false
;
}
@Override
protected
ViewGroup
.
LayoutParams
generateDefaultLayoutParams
()
{
return
new
LayoutParams
();
}
@Override
protected
ViewGroup
.
LayoutParams
generateLayoutParams
(
ViewGroup
.
LayoutParams
p
)
{
return
generateDefaultLayoutParams
();
}
@Override
protected
boolean
checkLayoutParams
(
ViewGroup
.
LayoutParams
p
)
{
return
p
instanceof
LayoutParams
&&
super
.
checkLayoutParams
(
p
);
}
@Override
public
ViewGroup
.
LayoutParams
generateLayoutParams
(
AttributeSet
attrs
)
{
return
new
LayoutParams
(
getContext
(),
attrs
);
}
private
class
PagerObserver
extends
DataSetObserver
{
@Override
public
void
onChanged
()
{
dataSetChanged
();
}
@Override
public
void
onInvalidated
()
{
dataSetChanged
();
}
}
/**
* Layout parameters that should be supplied for views added to a
* ViewPager.
*/
public
static
class
LayoutParams
extends
ViewGroup
.
LayoutParams
{
/**
* true if this view is a decoration on the pager itself and not
* a view supplied by the adapter.
*/
public
boolean
isDecor
;
/**
* Where to position the view page within the overall ViewPager
* container; constants are defined in {@link android.view.Gravity}.
*/
public
int
gravity
;
public
LayoutParams
()
{
super
(
FILL_PARENT
,
FILL_PARENT
);
}
public
LayoutParams
(
Context
context
,
AttributeSet
attrs
)
{
super
(
context
,
attrs
);
final
TypedArray
a
=
context
.
obtainStyledAttributes
(
attrs
,
LAYOUT_ATTRS
);
gravity
=
a
.
getInteger
(
0
,
Gravity
.
NO_GRAVITY
);
a
.
recycle
();
}
}
}
banner/src/main/res/layout/banner_imge_video.xml
View file @
ed47c127
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
android:layout_width=
"match_parent"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
android:layout_height=
"match_parent"
>
<
androidx.viewpager2.widget.ViewPager2
<
com.widget.imagebanner.view.ViewPager
android:id=
"@+id/view_pager"
android:id=
"@+id/view_pager"
android:layout_width=
"match_parent"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment