編輯:關於Android編程
畫廊在很多的App設計中都有,如下圖所示:
該例子是我沒事的時候寫的一個小項目,具體源碼地址請訪問https://github.com/AlexSmille/YingMi。
該畫廊類似封面的效果,滑到中間的圖片會慢慢變大,離開的View會慢慢的縮小,同時可設置滑動監聽和點擊監聽。
網上有很多例子都是通過Gallery實現的,而上例的實現是通過ViewPager實現,解決了性能優化的問題,今天特此把它抽出來,封裝一下,以便以後的方便使用。最終實現的效果如下:
使用方式
布局中添加該自定義控件
代碼中設置
代碼中設置分為以下幾個步驟:
查找控件 初始化數據 將需要顯示的數據設置到控件上 設置滑動監聽
public class MainActivity extends AppCompatActivity {
private CoverFlowViewPager mCover;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCover = (CoverFlowViewPager) findViewById(R.id.cover);
// 初始化數據
List list = new ArrayList<>();
for(int i = 0;i<10;i++){
ImageView img = new ImageView(this);
img.setBackgroundColor(Color.parseColor("#"+getRandColorCode()));
list.add(img);
}
//設置顯示的數據
mCover.setViewList(list);
// 設置滑動的監聽,該監聽為當前頁面滑動到中央時的索引
mCover.setOnPageSelectListener(new OnPageSelectListener() {
@Override
public void select(int position) {
Toast.makeText(getApplicationContext(),position+"",Toast.LENGTH_SHORT).show();
}
});
}
/**
* 獲取隨機顏色,便於區分
* @return
*/
public static String getRandColorCode(){
String r,g,b;
Random random = new Random();
r = Integer.toHexString(random.nextInt(256)).toUpperCase();
g = Integer.toHexString(random.nextInt(256)).toUpperCase();
b = Integer.toHexString(random.nextInt(256)).toUpperCase();
r = r.length()==1 ? "0" + r : r ;
g = g.length()==1 ? "0" + g : g ;
b = b.length()==1 ? "0" + b : b ;
return r+g+b;
}
}
實現原理
實現過程中有兩個難點:
如何實現滑動過程中的放大與縮小 如何顯示ViewPager中未被顯示的頁面
如何實現滑動過程中的放大與縮小
在設置每一個
ViewPager 的頁面時,對每一個頁面都設置一個固定的
padding值,這樣每個頁面都會顯示縮小狀態。同時
ViewPager設置
addOnPageChangeListener(),滑動監聽,在該滑動監聽中會回調
ViewPager的滑動的狀態,滑動的偏移量等,根據滑動的偏移量進行放大縮小。及根據
padding值設置控件的顯示大小
如何顯示ViewPager中未被顯示的頁面
在
xml中有一個不常用的屬性
android:clipChildren,是否限制子View的顯示。設置為
false,則子View的顯示不受父控件的限制。
代碼實現
編寫控件的布局文件
一個相對布局中嵌入一個
ViewPager,相對布局用於確定顯示的范圍,
ViewPager用以實現滑動,放大縮小等。
創建CoverFlowViewPager,加載布局
/**
*
* 實現封面浏覽
* Created by alex_mahao on 2016/8/25.
*/
public class CoverFlowViewPager extends RelativeLayout implements OnPageSelectListener {
/**
* 用於左右滾動
*/
private ViewPager mViewPager;
public CoverFlowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.widget_cover_flow,this);
mViewPager = (ViewPager) findViewById(R.id.vp_conver_flow);
//init();
}
查找控件,並加載布局。
編寫適配器,實現滑動的監聽
既然有了
ViewPager,那麼肯定要有適配器
Adapter。因為我們要在滑動監聽中,根據偏移量操作每一個子元素,放大或縮小,而對於子元素,當然適配器最容易獲取,所以將
Adapter實現了
ViewPager的滑動監聽接口。
/**
* 滾動的適配器
* Created by alex_mahao on 2016/8/25.
*/
public class CoverFlowAdapter extends PagerAdapter implements ViewPager.OnPageChangeListener {
/**
* 默認縮小的padding值
*/
public static int sWidthPadding;
public static int sHeightPadding;
/**
* 子元素的集合
*/
private List mViewList;
/**
* 滑動監聽的回調接口
*/
private OnPageSelectListener listener;
/**
* 上下文對象
*/
private Context mContext;
public CoverFlowAdapter(List mImageViewList, Context context) {
this.mViewList = mImageViewList;
mContext = context;
// 設置padding值
sWidthPadding = dp2px(24);
sHeightPadding = dp2px(32);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mViewList.get(position));
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = mViewList.get(position);
container.addView(view);
return view;
}
@Override
public int getCount() {
return mViewList == null ? 0 : mViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// 該方法回調ViewPager 的滑動偏移量
if (mViewList.size() > 0 && position < mViewList.size()) {
//當前手指觸摸滑動的頁面,從0頁滑動到1頁 offset越來越大,padding越來越大
Log.i("info", "重新設置padding");
int outHeightPadding = (int) (positionOffset * sHeightPadding);
int outWidthPadding = (int) (positionOffset * sWidthPadding);
// 從0滑動到一時,此時position = 0,其應該是縮小的,符合
mViewList.get(position).setPadding(outWidthPadding, outHeightPadding, outWidthPadding, outHeightPadding);
// position+1 為即將顯示的頁面,越來越大
if (position < mViewList.size() - 1) {
int inWidthPadding = (int) ((1 - positionOffset) * sWidthPadding);
int inHeightPadding = (int) ((1 - positionOffset) * sHeightPadding);
mViewList.get(position + 1).setPadding(inWidthPadding, inHeightPadding, inWidthPadding, inHeightPadding);
}
}
}
@Override
public void onPageSelected(int position) {
// 回調選擇的接口
if (listener != null) {
listener.select(position);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
/**
* 當將某一個作為最中央時的回調
*
* @param listener
*/
public void setOnPageSelectListener(OnPageSelectListener listener) {
this.listener = listener;
}
/**
* dp 轉 px
*
* @param dp
* @return
*/
public int dp2px(int dp) {
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mContext.getResources().getDisplayMetrics());
return px;
}
}
改代碼分為兩部分,
PagerAdapter實現,滑動監聽的實現。
PagerAdapter的實現不在多說,最基礎的東西。重點在滑動監聽的實現。
滑動監聽有三個回調方法:其中
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)回調的便是
ViewPager的滑動得偏移量,我們再次動態的設置相應元素的
padding值,實現放大縮小。
onPageSelected()為選中的回調,通過自定義接口的方式回調給其調用者。後面會提。
初始化ViewPager
既然有了適配器,那麼自然就開始編寫適配器的部分:
/**
* 初始化方法
*/
private void init() {
// 構造適配器,傳入數據源
mAdapter = new CoverFlowAdapter(mViewList,getContext());
// 設置選中的回調
mAdapter.setOnPageSelectListener(this);
// 設置適配器
mViewPager.setAdapter(mAdapter);
// 設置滑動的監聽,因為adpter實現了滑動回調的接口,所以這裡直接設置adpter
mViewPager.addOnPageChangeListener(mAdapter);
// 自己百度
mViewPager.setOffscreenPageLimit(5);
// 設置觸摸事件的分發
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 傳遞給ViewPager 進行滑動處理
return mViewPager.dispatchTouchEvent(event);
}
});
}
注釋很詳細,唯一需要解釋的便是事件的分發。
我們的
ViewPager的大小是固定的,只有中間的顯示區域,那麼對於手指在兩個側邊滑動時,
ViewPager自然接受不到觸摸事件,通過設置外層相對布局的觸摸事件監聽,將觸摸的事件傳遞到
ViewPager,實現滑動
ViewPager之外區域時,
ViewPager仍能夠實現對應的滑動。
數據源的包裝
適配器有了,
ViewPager也有了,那麼只剩下數據源了。
因為我們是根據設置
padding值實現的,那麼對於需要顯示的控件,他的背景將無法實現放大縮小,所以對控件在包裝一層外部控件,這樣設置外部控件的
padding值,自然需要顯示的控件會放大縮小。
/**
* 設置顯示的數據,進行一層封裝
* @param lists
*/
public void setViewList(List lists){
if(lists==null){
return;
}
mViewList.clear();
for(View view:lists){
FrameLayout layout = new FrameLayout(getContext());
// 設置padding 值,默認縮小
layout.setPadding(CoverFlowAdapter.sWidthPadding,CoverFlowAdapter.sHeightPadding,CoverFlowAdapter.sWidthPadding,CoverFlowAdapter.sHeightPadding);
layout.addView(view);
mViewList.add(layout);
}
// 刷新數據
mAdapter.notifyDataSetChanged();
}
選中監聽的回調
當我們滑動時,可能會根據不同的滑動,顯示不同的數據。
通過設置滑動監聽之後,對
onPageSelected實現層層的接口回調。
接口的定義
OnPageSelectListener
public interface OnPageSelectListener {
void select(int position);
}
CoverFlowAdapter中添加回調
@Override
public void onPageSelected(int position) {
// 回調選擇的接口
if (listener != null) {
listener.select(position);
}
}
CoverFlowViewPager中添加回調
// 顯示的回調
@Override
public void select(int position) {
if(listener!=null){
listener.select(position);
}
}
點擊事件的設置
直接對數據源循環設置監聽即可
安卓v7支持包下的ListView替代品————RecyclerViewRecyclerView這個控件也出來很久了,相信
Android下拉刷新庫,利用viewdraghelper實現。集成了下拉刷新,底部加載更多,以及剛進入加載數據的loadview。包括了listview與g
這篇來介紹一下適配器模式(Adapter Pattern),適配器模式在開發中使用的頻率也是很高的,像 ListView 和 RecyclerView 的 Adapter
這一次我們將會實現一個完整純粹的自定義控件,而不是像之前的組合控件一樣,拿系統的控件來實現;計劃分為三部分:自定義控件的基本部分,自定義控件的觸摸事件的處理和自定義控件的