編輯:關於Android編程
在這篇文章中,我將介紹如何實現列表中的視頻播放。在流行的應用,如Facebook,Instagram的或Magisto的工作原理相同:
Facebook的:
Magisto的:
Instagram的:
這篇文章是基於開源項目:VideoPlayerManager。
所有代碼和工作示例是存在的。在這篇文章中的很多東西會被跳過,因此,如果有人真的需要理解它是如何工作最好是下載源代碼和閱讀在你的IDE的源代碼的文章。但即使沒有代碼這篇文章將有利於與我們正在處理此理解。
為了實現所需要的,我們必須解決兩個問題:
我們要管理的視頻播放。在Android中,我們有一個類MediaPlayer.class與SurfaceView協同工作,並且可以播放視頻。但它也有很多缺點。我們不能在列表中使用通常的VideoView。VideoView延伸SurfaceView和SurfaceView不具有的UI同步緩沖器。所有這一切將導致我們到視頻正在播放嘗試,當你滾動它趕上名單的情況。同步緩存存在於TextureView但沒有VideoView是基於TextureView在Android的SDK版本15. 因此,我們需要擴展TextureView並與Android的MediaPlayer工作的看法。另外,幾乎所有的方法,從(准備,啟動,停止等…) MediaPlayer的基本上都是調用與硬件配合本地方法。硬件可以是棘手的,如果將做任何工作時間超過16毫秒(它肯定會),那麼我們將看到一個滯後的列表。這就是為什麼需要從後台線程調用它們。
我們還需要知道哪些鑒於滾動名單上的當前活動,可以切換回放時,用戶滾動。所以基本上我們必須跟蹤滾動,並確定最引人注目的觀點,如果它的變化。
在這裡,我們的目標是提供以下功能:
假設電影播放上。用戶滾動列表中的列表和新產品變得比該視頻播放所述一個更為明顯。所以,現在我們必須停止現有的視頻播放,並開始新的。
其主要功能是:停止播放以前,並開始播放新老一停之後。
這裡是它如何工作的視頻采樣:當您按視頻縮略圖 - 當前視頻播放停止,另一個開始。
我們需要實現的第一件事情是一個基於TextureView的VideoView。我們不能在滾動列表中使用VideoView。因為視頻渲染會搞砸了,如果用戶在播放過程中滾動我們的名單。
我分這個任務分為幾個部分:
創建一個ScalableTextureView。它是TextureView的後代,並且知道如何調整表面紋理(在重放運行該表面紋理),並提供了類似的ImageView scaleType幾個選項。
public enum ScaleType {
CENTER_CROP, TOP, BOTTOM, FILL
}
創建VideoPlayerView。這是ScalableTextureView的後裔,它包含了所有與MediaPlayer.class的功能。這個自定義視圖封裝MediaPlayer.class,並提供了非常相似的VideoView的API。它具有所有被直接調用MediaPlayer的方法:的setDataSource,准備,啟動,停止,暫停,恢復,釋放。
視頻播放經理與負責調用的MediaPlayer的方法MessagesHandlerThread一起工作。我們需要調用的方法類似制備(),啟動()等在一個單獨的線程,因為它們被直接連接到設備的硬件。並有情況下,當我們叫MediaPlayer.reset()在UI線程,但出現了一些問題的球員,這種方法是阻塞UI線程近4分鐘!這就是為什麼我們沒有使用異步MediaPlayer.prepareAsync,我們可以使用同步MediaPlayer.prepare。我們是在一個單獨的線程同步無所不為。
與啟動新的播放流。下面是幾個步驟做的MediaPlayer:
以前停止播放。它是通過調用MediaPlayer.stop()
方法來實現。 通過調用MediaPlayer.reset()
方法重置的MediaPlayer。我們需要做的是因為在滾動列表視圖可能會被重用,我們希望有發布的所有資源。 通過調用MediaPlayer.release()
方法釋放的MediaPlayer。 清除的MediaPlayer的實例。當這種觀點新的播放應該開始新的MediaPlayer實例將被創建。 新的最明顯的視圖創建實例上的MediaPlayer。 通過調用MediaPlayer.setDataSource(字符串URL)
設置新的MediaPlayer的數據源。 呼叫MediaPlayer.prepare()
。沒有必要使用異步MediaPlayer.prepareAsync()
。 呼叫MediaPlayer.start()
等待實際回放啟動。
所有這些行動被包裝成在一個單獨的線程處理的消息,例如這是停止的消息。它調用VideoPlayerView.stop()
,最終調用MediaPlayer.stop()
。我們需要自定義消息,因為我們可以設置當前狀態。我們知道它是停止或已停止否則後果不堪設想。它可以幫助我們來控制哪些消息是現在進行時,我們可以做些什麼,如果我們需要,例如,開始新的播放內容。
/**
* This PlayerMessage calls {@link MediaPlayer#stop()} on the instance that is used inside {@link VideoPlayerView}
*/
public class Stop extends PlayerMessage {
public Stop(VideoPlayerView videoView, VideoPlayerManagerCallback callback) {
super(videoView, callback);
}
@Override
protected void performAction(VideoPlayerView currentPlayer) {
currentPlayer.stop();
}
@Override
protected PlayerMessageState stateBefore() {
return PlayerMessageState.STOPPING;
}
@Override
protected PlayerMessageState stateAfter() {
return PlayerMessageState.STOPPED;
}
}
如果我們需要啟動新的回放,我們只是呼吁VideoPlayerManager的方法。並增加了以下一組信息到MessagesHandlerThread的:
// pause the queue processing and check current state
// if current state is "started" then stop old playback
mPlayerHandler.addMessage(new Stop(mCurrentPlayer, this));
mPlayerHandler.addMessage(new Reset(mCurrentPlayer, this));
mPlayerHandler.addMessage(new Release(mCurrentPlayer, this));
mPlayerHandler.addMessage(new ClearPlayerInstance(mCurrentPlayer, this));
// set new video player view
mPlayerHandler.addMessage(new SetNewViewForPlayback(newVideoPlayerView, this));
// start new playback
mPlayerHandler.addMessages(Arrays.asList(
new CreateNewPlayerInstance(videoPlayerView, this),
new SetAssetsDataSourceMessage(videoPlayerView, assetFileDescriptor, this), // I use local file for demo
new Prepare(videoPlayerView, this),
new Start(videoPlayerView, this)
));
// resume queue processing
這些消息同步運行,這就是為什麼我們可以在任何時候暫停隊列處理和發布新的消息,例如:
目前的電影是在准備狀態(MedaiPlayer.prepare()
被調用,並MediaPlayer.start()
在隊列中等待)和用戶滾動列表,所以我們需要在一個新的視圖開始播放。在這種情況下我們:
好了,我們必須運行在我們需要的方式播放公用事業:停止播放以前,只是再開始下一個。
下面是該庫的依賴關系的gradle:
dependencies {
compile 'com.github.danylovolokh:video-player-manager:0.2.0'
}
第一個問題是管理視頻播放。第二個問題是跟蹤哪些觀點是最明顯和回放切換到這一觀點。
有一個叫ListItemsVisibilityCalculator及其實施SingleListViewItemActiveCalculator做所有作業的實體。
即在適配器使用必須實現的ListItem接口,以便計算列表中的項目的知名度模型類:
/**
* A general interface for list items.
* This interface is used by {@link ListItemsVisibilityCalculator}
*
* @author danylo.volokh
*/
public interface ListItem {
/**
* When this method is called, the implementation should provide a
* visibility percents in range 0 - 100 %
* @param view the view which visibility percent should be
* calculated.
* Note: visibility doesn't have to depend on the visibility of a
* full view.
* It might be calculated by calculating the visibility of any
* inner View
*
* @return percents of visibility
*/
int getVisibilityPercents(View view);
/**
* When view visibility become bigger than "current active" view
* visibility then the new view becomes active.
* This method is called
*/
void setActive(View newActiveView, int newActiveViewPosition);
/**
* There might be a case when not only new view becomes active,
* but also when no view is active.
* When view should stop being active this method is called
*/
void deactivate(View currentView, int position);
}
該ListItemsVisibilityCalculator跟蹤滾動的方向,並計算在運行項目的可見性。該項目的知名度可能取決於列表中的單個項目內部的任何視圖。這是由你來實現getVisibilityPercents()方法。
有樣品演示應用該方法的默認實現:
/**
* This method calculates visibility percentage of currentView.
* This method works correctly when currentView is smaller then it's enclosure.
* @param currentView - view which visibility should be calculated
* @return currentView visibility percents
*/
@Override
public int getVisibilityPercents(View currentView) {
int percents = 100;
currentView.getLocalVisibleRect(mCurrentViewRect);
int height = currentView.getHeight();
if(viewIsPartiallyHiddenTop()){
// view is partially hidden behind the top edge
percents = (height - mCurrentViewRect.top) * 100 / height;
} else if(viewIsPartiallyHiddenBottom(height)){
percents = mCurrentViewRect.bottom * 100 / height;
}
return percents;
}
因此,每個視圖需要知道如何計算其知名度百分比。SingleListViewItemActiveCalculator將投票從滾動時發生這樣的實現不應該是非常沉重的每個視圖此值。
當任何鄰居項目的知名度超過了當前活動項目的知名度SETACTIVE方法將被調用。而當它是我們應該切換的再現。
此外,還有可以作為ListItemsVisibilityCalculator和ListView或RecyclerView之間的適配器的ItemsPositionGetter。這樣ListItemsVisibilityCalculator不知道,如果是的ListView或RecyclerView。它只是它的工作。但它需要一個通過ItemsPositionGetter提供了一些信息:
/**
* This class is an API for {@link ListItemsVisibilityCalculator}
* Using this class is can access all the data from RecyclerView /
* ListView
*
* There is two different implementations for ListView and for
* RecyclerView.
* RecyclerView introduced LayoutManager that's why some of data moved
* there
*
* Created by danylo.volokh on 9/20/2015.
*/
public interface ItemsPositionGetter {
View getChildAt(int position);
int indexOfChild(View view);
int getChildCount();
int getLastVisiblePosition();
int getFirstVisiblePosition();
}
有那種邏輯的模型是搞亂了一下從模型中分離業務邏輯的想法。但有一些修改它可能被分離。順便說一句,它工作正常,現在連怎麼回事。
下面是簡單的電影,顯示它是如何工作:
下面是一個utils的Gradle依賴關系:
dependencies {
compile 'com.github.danylovolokh:list-visibility-utils:0.2.0'
}
現在我們有解決我們所需要的一切兩個庫。讓我們將它們結合起來,以獲得我們所需要的功能。
在這裡,從使用RecyclerView片段的代碼:
初始化ListItemsVisibilityCalculator,並傳遞一個參考到一個列表吧。
/**
* Only the one (most visible) view should be active (and playing).
* To calculate visibility of views we use {@link SingleListViewItemActiveCalculator}
*/
private final ListItemsVisibilityCalculator mVideoVisibilityCalculator = new SingleListViewItemActiveCalculator(
new DefaultSingleItemCalculatorCallback(), mList);
DefaultSingleItemCalculatorCallback只是調用ListItem.setActive方法時,活動視圖變化,但你可以自己覆蓋它,做任何你需要:
/**
* Methods of this callback will be called when new active item is found {@link Callback#activateNewCurrentItem(ListItem, View, int)}
* or when there is no active item {@link Callback#deactivateCurrentItem(ListItem, View, int)} - this might happen when user scrolls really fast
*/
public interface Callback{
void activateNewCurrentItem(T item, View view, int position);
void deactivateCurrentItem(T item, View view, int position);
}
初始化VideoPlayerManager。
/**
* Here we use {@link SingleVideoPlayerManager}, which means that only one video playback is possible.
*/
private final VideoPlayerManager mVideoPlayerManager = new SingleVideoPlayerManager(new PlayerItemChangeListener() {
@Override
public void onPlayerItemChanged(MetaData metaData) {
}
});
設置上滾動監聽器RecyclerView並通過滾動的事件列表中的知名度utils的。
@Override
public void onScrollStateChanged(RecyclerView view, int scrollState) {
mScrollState = scrollState;
if(scrollState == RecyclerView.SCROLL_STATE_IDLE && mList.isEmpty()){
mVideoVisibilityCalculator.onScrollStateIdle(
mItemsPositionGetter,
mLayoutManager.findFirstVisibleItemPosition(),
mLayoutManager.findLastVisibleItemPosition());
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if(!mList.isEmpty()){
mVideoVisibilityCalculator.onScroll(
mItemsPositionGetter,
mLayoutManager.findFirstVisibleItemPosition(),
mLayoutManager.findLastVisibleItemPosition() -
mLayoutManager.findFirstVisibleItemPosition() + 1,
mScrollState);
}
}
});
創建ItemsPositionGetter。
ItemsPositionGetter mItemsPositionGetter =
new RecyclerViewItemPositionGetter(mLayoutManager, mRecyclerView);
我們呼吁在onResume的方法來啟動,只要我們打開屏幕計算最明顯的項目。
@Override
public void onResume() {
super.onResume();
if(!mList.isEmpty()){
// need to call this method from list view handler in order to have filled list
mRecyclerView.post(new Runnable() {
@Override
public void run() {
mVideoVisibilityCalculator.onScrollStateIdle(
mItemsPositionGetter,
mLayoutManager.findFirstVisibleItemPosition(),
mLayoutManager.findLastVisibleItemPosition());
}
});
}
}
就是這樣。我們有一組正在玩的滾動列表中的視頻:
基本上,這是最重要的部分僅僅是解釋。有一個在這裡的示例應用程序有更多的代碼:
https://github.com/danylovolokh/VideoPlayerManager
請看到源代碼的更多細節。謝謝!
本文實例講述了Android編程開發之seekBar采用handler消息處理操作的方法。分享給大家供大家參考,具體如下:該案例簡單實現進度條可走,可拖拽的功能,下面請看
小米手環2支持哪些手機?小米手環2准備開放購買了,相比於小米手環一代,新款的小米手環2更值得大家購買,那麼在購買之前,先要了解小米手環2是否支持自己的手機,
前幾天一同學項目中的某個功能需要ListView+EditText來實現,希望我給他寫個Demo,自己就隨手寫了一個小的Demo。後來想了想覺得這個功能其實挺常用的,而且
構建本地單元測試如果你的單元測試沒有依賴或者僅僅有簡單的Android依賴,你應當在本地開發器上運行你的測試。這種測試方法很高效,因為它讓你避免每次運行測試時加載目標Ap