仿专题订阅功能
来源:互联网 发布:日常英语口语视频软件 编辑:程序博客网 时间:2024/06/06 22:56
在Android开发中,有些时候会涉及到专题订阅,订阅专题无非是添加/移除专题。而我们的产品的订阅功能稍微有点不同,专题数默认7个,只能替换专题,不能够取消/新添专题,这里给出展示如下图:
实现过程如下:
1、自定义专题订阅容器,涉及到标签的移动,为了更灵活的定义标签位置,继承了相对布局RelativeLayout,将自定义布局命名为DraggingViewGroup;
2、定义专题的宽度,专题的高度在代码中写死,每行定义多少个专题也是确定了(这里是4个),通过DraggingViewGroup宽计算每个专题的宽,重写OnMeasure方法;
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (heightMode != MeasureSpec.EXACTLY) { // 指定默认高度 height = ((WindowManager) mContext .getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay().getHeight() / 2; } int widthMode = MeasureSpec.getMode(widthMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { // 指定默认宽度 width = ((WindowManager) mContext .getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay().getWidth(); } mTextViewWidth = (width - mLeftMergin - mRightMergin - (mTVCountForOneLine - 1) * mHorizontalBlankWithTextView) / mTVCountForOneLine; setMeasuredDimension(width, height); }
其中,mTextViewWidth是专题宽,width是DraggingViewGroup布局宽,mLeftMergin,mRightMergin是布局内偏移位置,mTVCountForOneLine是每行的专题数量,mHorizontalBlankWithTextView是专题横向间距。
3、添加并显示专题
这里添加的专题是个List数组,给定专题名称列表后,便会生成专题信息;
public void addTextLabelList(List<String> labelNames) { mLabelNames = labelNames; if (mLabelNames == null || mLabelNames.size() == 0) { return; } post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub int startX = mLeftMergin; int startY = mTopMergin + mLabelImageHeight; int childCount = mLabelNames.size(); int curTvCount = 1; for (int i = 0; i < childCount; i++, curTvCount++) { TextView tv = addTextLabel(mLabelNames.get(i), startX, startY); tv.setId(i); mLabelPos.append(i, new Point(startX, startY)); mLabelViews.append(i, tv); startX += mTextViewWidth + mHorizontalBlankWithTextView; if (curTvCount > mSelectedTopicSize) { // 备选标签 if (curTvCount != (mSelectedTopicSize + 1) && (curTvCount + 1) % mTVCountForOneLine == 0) { startX = mLeftMergin; startY += mCommonTVHeight + mVericalBlankWithTextView; } continue; } if (curTvCount % mTVCountForOneLine == 0) { // 已选标签 startX = mLeftMergin; startY += mCommonTVHeight + mVericalBlankWithTextView; } if (curTvCount == mSelectedTopicSize) { // 已选标签为默认数量(7个)时,充值下个标签的位置 startX = mLeftMergin; startY = firstDividerLineYPos + mTopMergin + mLabelImageHeight + mVericalBlankWithTextView; } } mTv = createBaseTextView("", 13, Color.parseColor("#696969")); LayoutParams lp = new LayoutParams(mTextViewWidth, mCommonTVHeight); lp.leftMargin = mLeftMergin + 2 * mLabelImageWidth; lp.topMargin = firstDividerLineYPos + mVericalBlankWithTextView; mTv.setLayoutParams(lp); mTv.setBackgroundResource(R.drawable.normal_label_bg); mTv.setVisibility(View.GONE); addView(mTv); } }); }
其中,45行前的代码都是在添加专题到容器,并且计算下一个专题的位置,45行-53行,是添加一个临时的专题控件,这个控件将随手势移动,可以看顶部的动图,给用户的感觉是选择的那个专题随手势移动;在这段代码中调用了addTextLabel方法,该方法定义了专题的基本信息,并且固定了专题的显示位置,如下代码;
/** * 添加标签 * * @param labelName * ,标签名称 * @param l * ,左侧位置 * @param t * ,顶部位置 * @return */ public TextView addTextLabel(String labelName, int l, int t) { TextView tv = createBaseTextView(labelName, 13, Color.parseColor("#696969")); tv.setLayoutParams(new LayoutParams(mTextViewWidth, mCommonTVHeight)); tv.setBackgroundResource(R.drawable.normal_label_bg); addView(tv); setPosition(tv, l, t); return tv; }
设定专题的显示位置;
/** * 设置视图的位置 * * @param v * ,被设置的视图 * @param l * ,左边位置 * @param t * ,顶部位置 */ private void setPosition(View v, int l, int t) { int parentWidth = this.getMeasuredWidth(); int parentHeight = this.getMeasuredHeight(); if (l < 0) l = 0; else if ((l + v.getMeasuredWidth()) >= parentWidth) { l = parentWidth - v.getMeasuredWidth(); } if (t < 0) t = 0; else if ((t + v.getHeight()) >= parentHeight) { t = parentHeight - v.getMeasuredHeight(); } int r = l + v.getMeasuredWidth(); int b = t + v.getMeasuredHeight(); v.layout(l, t, r, b); RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) v .getLayoutParams(); params.leftMargin = l; params.topMargin = t; v.setLayoutParams(params); }
4、移动专题
<1>确定某个点是否选中了某个专题
要移动专题,首先要清楚点击的位置选中的是哪个专题?如下方法inRangeOfView便是用以判断某个点(x,y)是否在某个专题内,除了判断当前点是否在某个专题布局范围内,还记录下了该点对应的专题的viewId,并改变了对应专题的background;
/** * 判断当前点,是否在某个标签内 * * @param x * @param y * @param action * ,当前手势是向下按下状态?移动状态? * @return */ private boolean inRangeOfView(int x, int y, int action) { boolean isInRangeOfView = false; try { int childCount = getChildCount(); // 这里-1,是因为最后添加了一个可移动的textview for (int i = mNewAddViewIndex; i < childCount - 1; i++) { View view = getChildAt(i); if (view.getId() != mActionDownViewId) { view.setBackgroundResource(R.drawable.normal_label_bg); } Rect rect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); if (rect.contains(x, y)) { if (action == ACTION_DOWN) { mActionDownViewId = view.getId(); mTv.setText(((TextView) view).getText().toString()); } else if (action == ACTION_MOVE) { mActionMoveViewId = view.getId(); } view.setBackgroundResource(R.drawable.cross_label_bg); isInRangeOfView = true; } } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } // 曾经有选择的textview,现在横过了(即没有在某个textview上方),这时需要把值重置 if (!isInRangeOfView) { mActionMoveViewId = -1; } return isInRangeOfView; }
<2>记录点击时,选中的专题
当用户点击屏幕上任意一点(x,y)时,如果该点恰好是某个专题控件位置内时,将mTV(上文提到这个控件用以更随手指移动,给用户的体验是选中的专题随用户手指移动)移动到点击的位置;
private boolean actionDownInLabel(int x, int y) { if (inRangeOfView(x, y, ACTION_DOWN)) { setPosition(mTv, x - mTv.getMeasuredWidth() / 2, y - mTv.getMeasuredHeight() / 2); mTv.setTranslationX(0); mTv.setTranslationY(0); mTv.setVisibility(View.VISIBLE); return true; } mActionDownViewId = -1; return false; }
该actionDownInLabel方法的调用位置在OnTouchEvent的ACTION_DOWN条件下,如果返回true表示将由onTouchEvent消费该点击事件,否则交由上一层处理;
@Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return actionDownInLabel((int) event.getX(), (int) event.getY()); case MotionEvent.ACTION_MOVE: int x = (int) event.getX(); int y = (int) event.getY(); actionMove(x - mTv.getWidth() / 2, y - mTv.getHeight() / 2); break; case MotionEvent.ACTION_UP: changeLabelTextViewPosition(); break; } return true; }
<3>移动选中的专题
假如点击时恰好选中了某个专题,接下来的action_move事件将继续交由OnToucheEvent处理,这时候调用actionMove方法,该方法里调用了setPosition方法不断的重设mTv的位置,是mTv跟随用户手指移动,同时继续调用inRangeOfView方法(上文提到该方法功能——判断当前点是否在某个专题布局范围内,还记录下了该点对应的专题的viewId,并改变了对应专题的background),记录手指横跨某个专题时,被横跨专题背景将会变化,而且记录当前横跨的位置,以便交互两个专题的信息;
private void actionMove(int l, int t) { try { setPosition(mTv, l, t); inRangeOfView(l + mTv.getMeasuredWidth() / 2, t + mTv.getMeasuredHeight() / 2, ACTION_MOVE); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } }
5、交换选中专题的信息
若按下时已选中某个专题了,移动过程中,并未选中其他专题便松开手指了,这时候选中的专题将会回到自己的原来的位置上,如下代码第8行-第41行都是在实现这个逻辑处理;
若按下时已选中某个专题了,移动过程中,选中了其他的专题,然后松开手指,这个时候将要实现2个专题的信息交换,如下代码的第43行-第88行都是在实现这个效果;
/** 标签替换,标签替换动画效果 */ private void changeLabelTextViewPosition() { try { int dx = 0; int dy = 0; Point downPoint = mLabelPos.get(mActionDownViewId); final View downView = mLabelViews.get(mActionDownViewId); if (mActionMoveViewId == -1) { // 不需要两两标签替换,回到原来的位置 dx = downPoint.x - (int) mTv.getLeft(); dy = downPoint.y - (int) mTv.getTop(); mTv.animate().translationX(dx).translationY(dy) .setDuration(ANIMATE_TIME).start(); mTv.animate().setListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationRepeat(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationEnd(Animator animation) { // TODO Auto-generated method stub downView.setBackgroundResource(R.drawable.normal_label_bg); } @Override public void onAnimationCancel(Animator animation) { // TODO Auto-generated method stub } }); return; } Point movePoint = mLabelPos.get(mActionMoveViewId); if (downPoint == null || movePoint == null) { return; } View moveView = mLabelViews.get(mActionMoveViewId); if (downView == null || moveView == null) { return; } // 将第二个tv移动到第一个tv位置 dx = downPoint.x - movePoint.x; dy = downPoint.y - movePoint.y; moveView.setBackgroundResource(R.drawable.normal_label_bg); moveView.animate().translationX(dx).translationY(dy) .setDuration(ANIMATE_TIME).start(); // 将第一个tv移动到第二个tv dx = movePoint.x - (int) mTv.getLeft(); dy = movePoint.y - (int) mTv.getTop(); mTv.animate().translationX(dx).translationY(dy) .setDuration(ANIMATE_TIME).start(); mTv.animate().setListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationRepeat(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationEnd(Animator animation) { // TODO Auto-generated method stub reflashTextViewPosition(); } @Override public void onAnimationCancel(Animator animation) { // TODO Auto-generated method stub } }); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } }
6、重置专题视图
虽然用障眼法用mTv代替了按下选中的专题标签来实现专题移动效果,实际上,动画效果显示完毕后,要重置一次专题视图,将两个交换信息的专题控件重置回原来的显示位置,只要将两个控件显示的值(setText方法)交换即可。
/** * 刷新视图中文本位置及替换后的标签名称 */ private void reflashTextViewPosition() { try { TextView tvDownView = mLabelViews.get(mActionDownViewId); TextView tvMoveView = mLabelViews.get(mActionMoveViewId); if (tvDownView != null && tvMoveView != null) { tvDownView.setTranslationX(0); tvDownView.setTranslationY(0); tvDownView.setText(mLabelNames.get(mActionMoveViewId)); tvDownView.setBackgroundResource(R.drawable.normal_label_bg); setPosition(tvDownView, mLabelPos.get(mActionDownViewId).x, mLabelPos.get(mActionDownViewId).y); tvMoveView.setTranslationX(0); tvMoveView.setTranslationY(0); tvMoveView.setText(mLabelNames.get(mActionDownViewId)); tvMoveView.setBackgroundResource(R.drawable.normal_label_bg); setPosition(tvMoveView, mLabelPos.get(mActionMoveViewId).x, mLabelPos.get(mActionMoveViewId).y); } mTv.setVisibility(View.GONE); String tempString = mLabelNames.get(mActionMoveViewId); mLabelNames.set(mActionMoveViewId, mLabelNames.get(mActionDownViewId)); mLabelNames.set(mActionDownViewId, tempString); mActionDownViewId = -1; mActionMoveViewId = -1; } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } }
至此,整个专题订阅的已经讲完了,提供demo下载链接
- 仿专题订阅功能
- 邮件订阅功能实现
- RSS订阅功能
- RSS订阅功能二
- Redis 订阅功能实现
- rss订阅功能
- RSS订阅功能
- Redis 发布订阅功能
- 仿今日头条订阅频道
- 配置SQLServer2005ReportingService的订阅功能
- php实现rss订阅功能
- JavaEI推出rss订阅功能
- socket.io房间订阅功能
- reids实现订阅/发布功能
- redis实时通信,订阅功能
- redis 订阅与发布功能
- Jedis实现发布订阅功能
- Redis发布与订阅功能
- 积分
- leetcode 每日一题 110. Balanced Binary Tree
- 1256 乘法逆元
- React 入门实例教程 12个demo
- 关于error file: /boot/grub/i386-pc/normal.mod not found. Grub Rescue的修复问题
- 仿专题订阅功能
- PHP实现页面静态化——局部动态化
- 网页字体大小标准
- 深入浅出聊Unity3D项目优化:从Draw Calls到GC
- 微信分享的时候为怎么不生成缩略图呢?我来总结一下
- 关于java访问权限修饰词
- codeblocks单步调试简单操作
- 3、简单排序(冒泡、选择、插入排序)
- Spring配置错误java.lang.NoClassDefFoundError: org/springframework/jdbc/datasource/TransactionAwareDataS