Android之高仿今日頭條、網易新聞首頁動態改變tab
前言:
專案需要一個類似今日頭條或者網易新聞首頁動態改變tab(頻道欄目)的功能,進過一番折騰,目前已實現該功能。
先看看效果圖:
思路:
1,關於tab欄目橫著滑動功能控制元件的選擇,這裡我採用的HorizontalScrollView,每個tab採用動態建立的方式。至於為什麼沒有選擇流行的TabLayout,是因為專案後期需求需要每個tab有一個長按的響應事件,但是TabLayout的長按事件不知道怎麼回事,總是無法響應,(有空會去研究)。
2,對欄目進行編輯介面的功能介紹:
①欄目分為當前使用者欄目和當前使用者沒有選擇的欄目(更多欄目),採用兩個GridView使用,但是整體又是可以上下滑動的,所以兩個GridView的外層是一個ScrollView,需要解決嵌套出現的問題。②過拽排序(附有動畫效果),當用戶在使用者欄目長按時,會出現震動,其中的第一個是不允許排序的(不能拖動),更多欄目只有點選事件,當點選時會把當前的tab移動到使用者欄目。③編輯介面返回時,需要重新設定首頁的tab欄目資料。
3,對欄目進行本地資料儲存,記錄使用者的每次對tab進行的修改。
MainActivity.java程式碼:
public class MainActivity extends AppCompatActivity {
private ColumnHorizontalScrollView mColumnHorizontalScrollView; // 自定義HorizontalScrollView
private LinearLayout mRadioGroup_content; // 每個標題
private LinearLayout ll_more_columns; // 右邊+號的父佈局
private ImageView button_more_columns; // 標題右邊的+號
private RelativeLayout rl_column; // +號左邊的佈局:包括HorizontalScrollView和左右陰影部分
public ImageView shade_left; // 左陰影部分
public ImageView shade_right; // 右陰影部分
private int columnSelectIndex = 0; // 當前選中的欄目索引
private int mItemWidth = 0; // Item寬度:每個標題的寬度
private int mScreenWidth = 0; // 螢幕寬度
public final static int CHANNELREQUEST = 1; // 請求碼
public final static int CHANNELRESULT = 10; // 返回碼
// tab集合:HorizontalScrollView的資料來源
private ArrayList<ChannelItem> userChannelList = new ArrayList<ChannelItem>();
private ViewPager mViewPager;
private ArrayList<Fragment> fragments = new ArrayList<Fragment>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mScreenWidth = Utils.getWindowsWidth(this);
mItemWidth = mScreenWidth / 7; // 一個Item寬度為螢幕的1/7
initView();
}
private void initView() {
setContentView(R.layout.activity_main);
mColumnHorizontalScrollView = (ColumnHorizontalScrollView) findViewById(R.id.mColumnHorizontalScrollView);
mRadioGroup_content = (LinearLayout) findViewById(R.id.mRadioGroup_content);
ll_more_columns = (LinearLayout) findViewById(R.id.ll_more_columns);
rl_column = (RelativeLayout) findViewById(R.id.rl_column);
button_more_columns = (ImageView) findViewById(R.id.button_more_columns);
shade_left = (ImageView) findViewById(R.id.shade_left);
shade_right = (ImageView) findViewById(R.id.shade_right);
mViewPager = (ViewPager) findViewById(R.id.mViewPager);
// + 號監聽
button_more_columns.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent_channel = new Intent(getApplicationContext(), ChannelActivity.class);
startActivityForResult(intent_channel, CHANNELREQUEST);
}
});
setChangelView();
}
/**
* 當欄目項發生變化時候呼叫
*/
private void setChangelView() {
initColumnData();
initTabColumn();
initFragment();
}
/**
* 獲取Column欄目 資料
*/
private void initColumnData() {
userChannelList = ((ArrayList<ChannelItem>) ChannelManage.getManage(AppApplication.getApp()
.getSQLHelper()).getUserChannel());
}
/**
* 初始化Column欄目項
*/
private void initTabColumn() {
mRadioGroup_content.removeAllViews();
int count = userChannelList.size();
mColumnHorizontalScrollView.setParam(this, mScreenWidth, mRadioGroup_content, shade_left,
shade_right, ll_more_columns, rl_column);
for (int i = 0; i < count; i++) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mItemWidth,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = 5;
params.rightMargin = 5;
TextView columnTextView = new TextView(this);
columnTextView.setGravity(Gravity.CENTER);
columnTextView.setPadding(5, 5, 5, 5);
columnTextView.setId(i);
columnTextView.setText(userChannelList.get(i).getName());
columnTextView.setTextColor(getResources().getColorStateList(R.color.top_category_scroll_text_color_day));
if (columnSelectIndex == i) {
columnTextView.setSelected(true);
}
// 單擊監聽
columnTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 0; i < mRadioGroup_content.getChildCount(); i++) {
View localView = mRadioGroup_content.getChildAt(i);
if (localView != v) {
localView.setSelected(false);
} else {
localView.setSelected(true);
mViewPager.setCurrentItem(i);
}
}
Toast.makeText(getApplicationContext(), userChannelList.get(v.getId()).getName(), Toast.LENGTH_SHORT).show();
}
});
mRadioGroup_content.addView(columnTextView, i, params);
}
}
/**
* 初始化Fragment
*/
private void initFragment() {
fragments.clear();//清空
int count = userChannelList.size();
for (int i = 0; i < count; i++) {
NewsFragment newfragment = new NewsFragment();
fragments.add(newfragment);
}
NewsFragmentPagerAdapter mAdapetr = new NewsFragmentPagerAdapter(getSupportFragmentManager(), fragments);
mViewPager.setAdapter(mAdapetr);
mViewPager.addOnPageChangeListener(pageListener);
}
/**
* ViewPager切換監聽方法
*/
public ViewPager.OnPageChangeListener pageListener = new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int position) {
mViewPager.setCurrentItem(position);
selectTab(position);
}
};
/**
* 選擇的Column裡面的Tab
*/
private void selectTab(int tab_postion) {
columnSelectIndex = tab_postion;
for (int i = 0; i < mRadioGroup_content.getChildCount(); i++) {
View checkView = mRadioGroup_content.getChildAt(tab_postion);
int k = checkView.getMeasuredWidth();
int l = checkView.getLeft();
int i2 = l + k / 2 - mScreenWidth / 2;
mColumnHorizontalScrollView.smoothScrollTo(i2, 0);
}
//判斷是否選中
for (int j = 0; j < mRadioGroup_content.getChildCount(); j++) {
View checkView = mRadioGroup_content.getChildAt(j);
boolean ischeck;
if (j == tab_postion) {
ischeck = true;
} else {
ischeck = false;
}
checkView.setSelected(ischeck);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CHANNELREQUEST:
if (resultCode == CHANNELRESULT) {
setChangelView();
}
break;
default:
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
}
主頁是控制元件和資料初始化工作。可以橫向滑動的是自定義的HorizontalScrollView——ColumnHorizontalScrollView,程式碼如下:
public class ColumnHorizontalScrollView extends HorizontalScrollView {
/**
* 傳入整體佈局
*/
private View ll_content;
/**
* 傳入更多欄目選擇佈局
*/
private View ll_more;
/**
* 傳入拖動欄佈局
*/
private View rl_column;
/**
* 左陰影圖片
*/
private ImageView leftImage;
/**
* 右陰影圖片
*/
private ImageView rightImage;
/**
* 螢幕寬度
*/
private int mScreenWitdh = 0;
/**
* 父類的活動activity
*/
private Activity activity;
public ColumnHorizontalScrollView(Context context) {
super(context);
}
public ColumnHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ColumnHorizontalScrollView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
/**
* 在拖動的時候執行
*/
@Override
protected void onScrollChanged(int paramInt1, int paramInt2, int paramInt3, int paramInt4) {
// TODO Auto-generated method stub
super.onScrollChanged(paramInt1, paramInt2, paramInt3, paramInt4);
shade_ShowOrHide();
if (!activity.isFinishing() && ll_content != null && leftImage != null && rightImage != null && ll_more != null && rl_column != null) {
if (ll_content.getWidth() <= mScreenWitdh) {
leftImage.setVisibility(View.GONE);
rightImage.setVisibility(View.GONE);
}
} else {
return;
}
if (paramInt1 == 0) {
leftImage.setVisibility(View.GONE);
rightImage.setVisibility(View.VISIBLE);
return;
}
if (ll_content.getWidth() - paramInt1 + ll_more.getWidth() + rl_column.getLeft() == mScreenWitdh) {
leftImage.setVisibility(View.VISIBLE);
rightImage.setVisibility(View.GONE);
return;
}
leftImage.setVisibility(View.VISIBLE);
rightImage.setVisibility(View.VISIBLE);
}
/**
* 傳入父類佈局中的資原始檔
*/
public void setParam(Activity activity, int mScreenWitdh, View paramView1, ImageView paramView2, ImageView paramView3, View paramView4, View paramView5) {
this.activity = activity;
this.mScreenWitdh = mScreenWitdh;
ll_content = paramView1;
leftImage = paramView2;
rightImage = paramView3;
ll_more = paramView4;
rl_column = paramView5;
}
/**
* 判斷左右陰影的顯示隱藏效果
*/
public void shade_ShowOrHide() {
if (!activity.isFinishing() && ll_content != null) {
measure(0, 0);
//如果整體寬度小於螢幕寬度的話,那左右陰影都隱藏
if (mScreenWitdh >= getMeasuredWidth()) {
leftImage.setVisibility(View.GONE);
rightImage.setVisibility(View.GONE);
}
} else {
return;
}
//如果滑動在最左邊時候,左邊陰影隱藏,右邊顯示
if (getLeft() == 0) {
leftImage.setVisibility(View.GONE);
rightImage.setVisibility(View.VISIBLE);
return;
}
//如果滑動在最右邊時候,左邊陰影顯示,右邊隱藏
if (getRight() == getMeasuredWidth() - mScreenWitdh) {
leftImage.setVisibility(View.VISIBLE);
rightImage.setVisibility(View.GONE);
return;
}
//否則,說明在中間位置,左、右陰影都顯示
leftImage.setVisibility(View.VISIBLE);
rightImage.setVisibility(View.VISIBLE);
}
}
完成了滑動改變tab索引,左右陰影效果。
使用者欄目編輯介面程式碼
public class ChannelActivity extends GestureDetectorActivity implements AdapterView.OnItemClickListener {
/**
* 使用者欄目
*/
private DragGrid userGridView; // GridView
DragAdapter userAdapter; // 介面卡
ArrayList<ChannelItem> userChannelList = new ArrayList<ChannelItem>();
/**
* 其它欄目
*/
private OtherGridView otherGridView; // GridView
OtherAdapter otherAdapter; // 介面卡
ArrayList<ChannelItem> otherChannelList = new ArrayList<ChannelItem>(); // 資料來源
/**
* 是否在移動,由於是動畫結束後才進行的資料更替,設定這個限制為了避免操作太頻繁造成的資料錯亂。
*/
boolean isMove = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.channel);
initView();
initData();
}
/**
* 初始化資料
*/
private void initData() {
userChannelList = ((ArrayList<ChannelItem>) ChannelManage.getManage(AppApplication.getApp().getSQLHelper()).getUserChannel());
otherChannelList = ((ArrayList<ChannelItem>) ChannelManage.getManage(AppApplication.getApp().getSQLHelper()).getOtherChannel());
userAdapter = new DragAdapter(this, userChannelList);
userGridView.setAdapter(userAdapter);
otherAdapter = new OtherAdapter(this, otherChannelList);
otherGridView.setAdapter(otherAdapter);
//設定GRIDVIEW的ITEM的點選監聽
otherGridView.setOnItemClickListener(this);
userGridView.setOnItemClickListener(this);
}
/**
* 初始化佈局
*/
private void initView() {
userGridView = (DragGrid) findViewById(R.id.userGridView);
otherGridView = (OtherGridView) findViewById(R.id.otherGridView);
}
/**
* GRIDVIEW對應的ITEM點選監聽介面
*/
@Override
public void onItemClick(AdapterView<?> parent, final View view, final int position, long id) {
//如果點選的時候,之前動畫還沒結束,那麼就讓點選事件無效
if (isMove) {
return;
}
switch (parent.getId()) {
case R.id.userGridView:
//position為 0 的不進行任何操作
if (position != 0) {
final ImageView moveImageView = getView(view);
if (moveImageView != null) {
TextView newTextView = (TextView) view.findViewById(R.id.text_item);
final int[] startLocation = new int[2];
newTextView.getLocationInWindow(startLocation);
final ChannelItem channel = ((DragAdapter) parent.getAdapter()).getItem(position);
otherAdapter.setVisible(false);
//新增到最後一個
otherAdapter.addItem(channel);
new Handler().postDelayed(new Runnable() {
public void run() {
try {
int[] endLocation = new int[2];
//獲取終點的座標
otherGridView.getChildAt(otherGridView.getLastVisiblePosition()).getLocationInWindow(endLocation);
MoveAnim(moveImageView, startLocation, endLocation, channel, userGridView);
userAdapter.setRemove(position);
} catch (Exception localException) {
}
}
}, 50L);
}
}
break;
case R.id.otherGridView:
// 其它GridView
final ImageView moveImageView = getView(view);
if (moveImageView != null) {
TextView newTextView = (TextView) view.findViewById(R.id.text_item);
final int[] startLocation = new int[2];
newTextView.getLocationInWindow(startLocation);
final ChannelItem channel = ((OtherAdapter) parent.getAdapter()).getItem(position);
userAdapter.setVisible(false);
//新增到最後一個
userAdapter.addItem(channel);
new Handler().postDelayed(new Runnable() {
public void run() {
try {
int[] endLocation = new int[2];
//獲取終點的座標
userGridView.getChildAt(userGridView.getLastVisiblePosition()).getLocationInWindow(endLocation);
MoveAnim(moveImageView, startLocation, endLocation, channel, otherGridView);
otherAdapter.setRemove(position);
} catch (Exception localException) {
}
}
}, 50L);
}
break;
default:
break;
}
}
/**
* 點選ITEM移動動畫
*
* @param moveView
* @param startLocation
* @param endLocation
* @param moveChannel
* @param clickGridView
*/
private void MoveAnim(View moveView, int[] startLocation, int[] endLocation, final ChannelItem moveChannel,
final GridView clickGridView) {
int[] initLocation = new int[2];
//獲取傳遞過來的VIEW的座標
moveView.getLocationInWindow(initLocation);
//得到要移動的VIEW,並放入對應的容器中
final ViewGroup moveViewGroup = getMoveViewGroup();
final View mMoveView = getMoveView(moveViewGroup, moveView, initLocation);
//建立移動動畫
TranslateAnimation moveAnimation = new TranslateAnimation(
startLocation[0], endLocation[0], startLocation[1],
endLocation[1]);
moveAnimation.setDuration(300L);
//動畫配置
AnimationSet moveAnimationSet = new AnimationSet(true);
moveAnimationSet.setFillAfter(false);//動畫效果執行完畢後,View物件不保留在終止的位置
moveAnimationSet.addAnimation(moveAnimation);
mMoveView.startAnimation(moveAnimationSet);
moveAnimationSet.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
isMove = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
moveViewGroup.removeView(mMoveView);
// instanceof 方法判斷2邊例項是不是一樣,判斷點選的是DragGrid還是OtherGridView
if (clickGridView instanceof DragGrid) {
otherAdapter.setVisible(true);
otherAdapter.notifyDataSetChanged();
userAdapter.remove();
} else {
userAdapter.setVisible(true);
userAdapter.notifyDataSetChanged();
otherAdapter.remove();
}
isMove = false;
}
});
}
/**
* 獲取移動的VIEW,放入對應ViewGroup佈局容器
*
* @param viewGroup
* @param view
* @param initLocation
* @return
*/
private View getMoveView(ViewGroup viewGroup, View view, int[] initLocation) {
int x = initLocation[0];
int y = initLocation[1];
viewGroup.addView(view);
LinearLayout.LayoutParams mLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mLayoutParams.leftMargin = x;
mLayoutParams.topMargin = y;
view.setLayoutParams(mLayoutParams);
return view;
}
/**
* 建立移動的ITEM對應的ViewGroup佈局容器
*/
private ViewGroup getMoveViewGroup() {
ViewGroup moveViewGroup = (ViewGroup) getWindow().getDecorView();
LinearLayout moveLinearLayout = new LinearLayout(this);
moveLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
moveViewGroup.addView(moveLinearLayout);
return moveLinearLayout;
}
/**
* 獲取點選的Item的對應View,
*
* @param view
* @return
*/
private ImageView getView(View view) {
view.destroyDrawingCache();
view.setDrawingCacheEnabled(true);
Bitmap cache = Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
ImageView iv = new ImageView(this);
iv.setImageBitmap(cache);
return iv;
}
/**
* 退出時候儲存選擇後資料庫的設定
*/
private void saveChannel() {
ChannelManage.getManage(AppApplication.getApp().getSQLHelper()).deleteAllChannel();
ChannelManage.getManage(AppApplication.getApp().getSQLHelper()).saveUserChannel(userAdapter.getChannnelLst());
ChannelManage.getManage(AppApplication.getApp().getSQLHelper()).saveOtherChannel(otherAdapter.getChannnelLst());
}
@Override
public void onBackPressed() {
saveChannel();
if (userAdapter.isListChanged()) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
setResult(MainActivity.CHANNELRESULT, intent);
finish();
} else {
super.onBackPressed();
}
}
}
實現了拖動和新增動畫,排序功能。
剩下的就是介面卡和本地資料儲存程式碼,再不貼出來了。完整程式碼已上傳至github,關注公眾號“code小生”檢視原文,以及專案地址。