編輯:關於Android編程
ListView雖然使用廣泛,但系統原生的ListView顯然是不能滿足用戶在審美、功能上不斷提高的需求。不過也不要緊,Android完全可以定制化,讓我們非常方便地對原生ListView進行拓展、修改。於是,在開發者的創新下,ListView越來越豐富多彩,各種各樣的基於原生ListView的拓展讓人目不暇接。下面來看幾個常用的ListView拓展。
Android默認的ListView在滾動到頂端或者底端的時候,並沒有很好的提示。在Android5.X中,Google為這樣的行為只添加了一個半月形的陰影效果,如下圖所示。
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
可以看見這樣一個參數:maxOverScrollY,注釋中這樣寫道——Number of pixels to overscroll by in either direction along the Y axis。由此可以發現,雖然它的默認值是0,但其實只要修改這個參數的值,就可以讓ListView具有彈性了!所以,既然我們不知道為什麼Google不采用這樣的修改,那我們就自己來修改一下吧。重寫這個方法,並將maxOverScrollY改為設置的值——mMaxOverDistance,代碼如下所示。
@Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxOverDistance, isTouchEvent); }
這樣,通過對這個值得修改,就實現了一個具有彈性的ListView了。效果如下圖所示。
private void initView() { DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); float density = metrics.density; mMaxOverDistance = (int) (density * mMaxOverDistance); }
相信通過Google+的朋友應該非常熟悉這樣一個效果:當我們在ListView上滑動的時候,頂部的ActionBar或者Toolbar就會相應的隱藏或者顯示。這樣的效果一出現,各種App競相模仿,不得不說,Google的應用一直都是Android設計的風向標。
大家可以發現,在滾動前界面上加載了上方的標題欄和右下角的懸浮編輯按鈕,如下圖所示。
View header = new View(this); header.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.abc_action_bar_default_height_material))); mListView.addHeaderView(header);
在代碼中,通過使用abc_action_bar_default_height_material屬性獲取系統Actionbar的高度,並設置給HeaderView。另外,定義一個mTouchSlop變量來獲取系統認為的最低滑動距離,即超過這個距離的移動,系統就將其定義為滑動狀態了,對這個值得獲取非常簡單,代碼如下所示。
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
有了前面的准備工作,下面我們就可以判斷滑動的事件了,關鍵代碼如下所示。
@Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mFirstY = event.getY(); break; case MotionEvent.ACTION_MOVE: mCurrentY = event.getY(); if (mCurrentY - mFirstY > mTouchSlop) { // down if (mShow) { toolbarAnim(0); } mShow = !mShow; } else if (mCurrentY - mFirstY < mTouchSlop) { // up if (mShow) { toolbarAnim(1); } mShow = !mShow; } break; case MotionEvent.ACTION_UP: break; } return false;
代碼邏輯非常簡單,只是通過滑動點的坐標改變大小,來判斷移動的方向,並根據移動方向來執行不同的動畫效果。
有了前面的分析,實現這樣一個效果就非常簡單了,最後加上控制布局顯示隱藏的動畫,如下所示。
private void toolbarAnim(int flag) { if (mAnimator != null && mAnimator.isRunning()) { mAnimator.cancel(); } if (flag == 0) { mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(), 0); } else { mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(), -mToolbar.getHeight()); } mAnimator.start(); }
動畫也是最簡單的位移屬性動畫。不過這裡需要說一點題外話,這裡使用了Toorbar這樣一個新控件,Google已經推薦它來逐漸取代ActionBar了,因為它更加靈活。但是在使用的時候,一定要注意使用的theme一定是要NoActionBar的,不然會引起沖突。同時,不要忘記引入編譯,代碼如下。
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.4.0' }
運行程序後初始狀態如下圖所示,Toorbar顯示在最上方。
通常我們使用的ListView的每一項都具有相同的布局,所以展現出來的時候,除了數據不同,只要你不隱藏布局,其他的布局應該都是類似的。而我們熟知的QQ、微信等聊天App,在聊天界面,會展示至少兩種布局,即收到的消息和自己發送的消息,其實這樣的效果也是通過ListView來實現的,下面我們就來模仿一個聊天軟件的聊天列表界面,其效果如下圖所示。
@Override public int getItemViewType(int position) { return super.getItemViewType(position); } @Override public int getViewTypeCount() { return super.getViewTypeCount(); }
getItemViewType()方法用來返回第position個Item是何種類型,而getViewTypeCount()方法用來返回不同布局的總數。通過這兩個方法,再結合getView()方法,就可以很輕松地設計出上面的聊天布局了。
首先來實現兩個布局——chat_item_itemin和chat_item_itemout。布局大同小異,只是方向上有區別。需要注意的是,顯示聊天信息內容的TextView使用了9patch的圖片,這種圖片格式是Android中用來拉伸圖片的,你可以把它想象成在某些方向上拉伸卻不會失真、形變的圖片就可以了,布局代碼如下所示。由於in和out界面內容只是方向上的區別,這裡只貼出一個布局的代碼。
同時,為了封裝下聊天內容,便於在Adapter中獲取數據信息,我們封裝了一個Bean來保存聊天信息,代碼如下所示。
import android.graphics.Bitmap; /********************************************* * author: Blankj on 2016/7/25 14:01 * blog: http://blankj.com * e-mail: [email protected] *********************************************/ public class ChatListViewBean { private int type; private String text; private Bitmap icon; public ChatListViewBean() { } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Bitmap getIcon() { return icon; } public void setIcon(Bitmap icon) { this.icon = icon; } }
非常簡單,我們只是聲明了需要的信息並提供了get和set方法。
接下來,需要來完成最重要的BaseAdapter了,同樣使用ViewHolder模式來提高ListView的效率,並在getView()方法中進行布局類型的判斷,從而確定使用哪種布局,代碼如下所示。
import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.List; /********************************************* * author: Blankj on 2016/7/25 14:01 * blog: http://blankj.com * e-mail: [email protected] *********************************************/ public class ChatListViewAdapter extends BaseAdapter { private ListmData; private LayoutInflater mInflater; public ChatListViewAdapter(Context context, List data) { this.mData = data; mInflater = LayoutInflater.from(context); } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public int getItemViewType(int position) { ChatListViewBean bean = mData.get(position); return bean.getType(); } @Override public int getViewTypeCount() { return 2; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if (convertView == null) { viewHolder = new ViewHolder(); if (getItemViewType(position) == 0) { convertView = mInflater.inflate(R.layout.chat_item_itemin, null); viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon_in); viewHolder.text = (TextView) convertView.findViewById(R.id.text_in); } else { convertView = mInflater.inflate(R.layout.chat_item_itemout, null); viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon_out); viewHolder.text = (TextView) convertView.findViewById(R.id.text_out); } convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.icon.setImageBitmap(mData.get(position).getIcon()); viewHolder.text.setText(mData.get(position).getText()); return convertView; } public final class ViewHolder { public ImageView icon; public TextView text; } }
在以上代碼中,通過在getView()中判斷getItemType(position)的值來決定具體實例化哪一個布局,從而實現在一個ListView中多個布局內容的添加。最後,在測試的Activity裡面添加了一些測試代碼,來測試這個布局。
import android.app.Activity; import android.graphics.BitmapFactory; import android.os.Bundle; import android.widget.ListView; import java.util.ArrayList; import java.util.List; /********************************************* * author: Blankj on 2016/7/25 13:30 * blog: http://blankj.com * e-mail: [email protected] *********************************************/ public class ChatListViewTest extends Activity { private ListView mListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); mListView = (ListView) findViewById(R.id.lv_chat); ChatListViewBean bean1 = new ChatListViewBean(); bean1.setType(0); bean1.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.in_icon)); bean1.setText("Hello how are you?"); ChatListViewBean bean2 = new ChatListViewBean(); bean2.setType(1); bean2.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.out_icon)); bean2.setText("Fine thank you, and you?"); ChatListViewBean bean3 = new ChatListViewBean(); bean3.setType(0); bean3.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.in_icon)); bean3.setText("I am fine, too"); ChatListViewBean bean4 = new ChatListViewBean(); bean4.setType(1); bean4.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.out_icon)); bean4.setText("Bye bye"); ChatListViewBean bean5 = new ChatListViewBean(); bean5.setType(0); bean5.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.in_icon)); bean5.setText("See you"); Listdata = new ArrayList (); data.add(bean1); data.add(bean2); data.add(bean3); data.add(bean4); data.add(bean5); mListView.setAdapter(new ChatListViewAdapter(this, data)); } }
在測試代碼中,簡單地添加了一些模擬的聊天內容,並將信息封裝到設置的Bean對象中,最後運行程序,即可得到之前所示的聊天效果界面。
通常情況下,如果要動態地改變點擊Item的布局來達到一個Focus的效果,一般有兩種方法。一種是將兩種布局寫在一起,通過控制布局的顯示、隱藏,來達到切換布局的效果;另一種則是在getView()的時候,通過判斷來選擇加載不同的布局。兩種方法各有利弊,關鍵還是看使用的場合。下面就以第二種方式,來演示一下這樣的效果,程序運行後初始效果下圖所示,第一個Item為默認Focus狀態。
在這兩個方法中,可以根據Item位置的不同來設置不同的顯示圖片等信息,但這裡為了方便,就統一只顯示一張圖片。
下面回到BaseAdapter,在getView()方法中,通過判斷點擊的位置來改變相應的視圖,代碼如下所示。
@Override public View getView(int position, View convertView, ViewGroup parent) { LinearLayout layout = new LinearLayout(mContext); layout.setOrientation(LinearLayout.VERTICAL); if (mCurrentItem == position) { layout.addView(addFocusView(position)); } else { layout.addView(addNormalView(position)); } return layout; }
在以上代碼中,通過判斷當前CurrentItem是否是點擊的那個position,就可以動態控制顯示的布局了。當然,僅僅這樣是不夠的,因為getView()是在初始化的時候調用,後面再點擊Item的時候,並沒有再次調用getView()。所以,必須要讓ListView在點擊後,再刷新一次。於是我們請出了notifyDataSetChanged()方法來幫助實現刷新布局的功能,代碼如下所示。
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { mAdapter.setCurrentItem(position); mAdapter.notifyDataSetChanged(); } });
項目地址→ListViewExpandation
做開發的,最基本的調試要會,今天簡單做個步驟,希望對小白有幫助。網上很多教程講的都是使用這個按鈕進行調試今天我只講個簡單的吧。簡單流程:正常Run app也就是用&ldq
今天終於有點時間,來寫了一下: 為RecyclerView實現下拉刷新和上拉加載更多。今天會在前面的兩篇文章的基礎上:RecyclerView系列之(1):為Recycl
最近看到公司IOS的同事做了一個app打包工具給QA使用,極大的方便了QA的工作,也給開發節省了不少精力,不需要頻繁的接收QA的要求給QA打包新app做測試,防止編程思路
由於隨手拍項目想做成類似於美圖秀秀那種底部有一排Menu實現不同效果的功能,這裡先簡單介紹如何通過Menu實現打開相冊中的圖片、懷舊效果、浮雕效果、光照效果和素描效果.後