100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Android自定义View(广告栏上下滚动效果)

Android自定义View(广告栏上下滚动效果)

时间:2018-07-12 14:23:37

相关推荐

Android自定义View(广告栏上下滚动效果)

需求中涉及到的广告栏变化千变万化,这里,我们综合取材,有了下面的这篇文章。

开始的时候,我们使用的是MarqueeView,继承的ViewFlipper,但是会有一些bug,比如刷新数据时的重叠阴影等等

后来,考虑到后期的开发可能出现的修改,采用了自定义View继承LinearLayout来展示,代码实现如下:

Step1:自定义LimitScrollerView

import android.animation.Animator;import android.animation.AnimatorSet;import android.animation.ObjectAnimator;import android.content.Context;import android.content.res.TypedArray;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.LinearLayout;public class LimitScrollerView extends LinearLayout implements View.OnClickListener{private String TAG = "LimitScrollerView";private LinearLayout ll_content1, ll_content2;private LinearLayout ll_now, ll_down; //当前可见的,下面不可见的(切换)private int limit;//可见条目数量private int durationTime; //动画执行时间private int periodTime;//间隔时间private int scrollHeight; //滚动高度(控件高度)private int dataIndex;private boolean isCancel;//是否停止滚动动画private boolean boundData;//是否已经第一次绑定过数据private final int MSG_SETDATA = 1;private final int MSG_SCROL = 2;private Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {if(msg.what == MSG_SETDATA){boundData(true);}else if(msg.what == MSG_SCROL){if(isCancel)return;startAnimation();}}};public LimitScrollerView(Context context) {this(context, null);}public LimitScrollerView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public LimitScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs);}private void init(Context context, AttributeSet attrs){LayoutInflater.from(context).inflate(R.layout.limit_scroller, this, true);ll_content1 = (LinearLayout) findViewById(R.id.ll_content1);ll_content2 = (LinearLayout) findViewById(R.id.ll_content2);ll_now = ll_content1;ll_down = ll_content2;if(attrs!=null){TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LimitScroller);limit = ta.getInt(R.styleable.LimitScroller_limit, 1);durationTime = ta.getInt(R.styleable.LimitScroller_durationTime, 1000);periodTime = ta.getInt(R.styleable.LimitScroller_periodTime, 1000);ta.recycle(); //注意回收Log.v(TAG, "limit="+limit);Log.v(TAG, "durationTime="+durationTime);Log.v(TAG, "periodTime="+periodTime);}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//EXACTLY=1073741824//AT_MOST=-2147483648/* int specMode = MeasureSpec.getMode(heightMeasureSpec);int specSize = MeasureSpec.getSize(heightMeasureSpec);Log.w(TAG, "specMode="+specMode);Log.w(TAG, "specSize="+specSize);int newHeightSpec = MeasureSpec.makeMeasureSpec(specSize, MeasureSpec.EXACTLY);int childCount = ll_content1.getChildCount();if(childCount>0){View item = ll_content1.getChildAt(0);item.measure(widthMeasureSpec, newHeightSpec);Log.w(TAG, "条目高度="+ item.getMeasuredHeight());super.onMeasure(widthMeasureSpec, heightMeasureSpec);setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()/2);scrollHeight = getMeasuredHeight();return;}super.onMeasure(widthMeasureSpec, heightMeasureSpec);*/super.onMeasure(widthMeasureSpec, heightMeasureSpec);//设置高度为整体高度的一般,以达到遮盖预备容器的效果setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()/2);//此处记下控件的高度,此高度就是动画执行时向上滚动的高度scrollHeight = getMeasuredHeight();// Log.w(TAG, "getMeasuredWidth="+getMeasuredWidth());// Log.w(TAG, "getMeasuredHeight="+getMeasuredHeight());// Log.w(TAG, "scrollHeight="+scrollHeight);}private void startAnimation(){if(isCancel)return;Log.i(TAG, "滚动");//当前展示的容器,从当前位置(0),向上滚动scrollHeightObjectAnimator anim1 = ObjectAnimator.ofFloat(ll_now, "Y",ll_now.getY(), ll_now.getY()-scrollHeight);//预备容器,从当前位置,向上滚动scrollHeightObjectAnimator anim2 = ObjectAnimator.ofFloat(ll_down, "Y",ll_down.getY(), ll_down.getY()-scrollHeight);AnimatorSet animSet = new AnimatorSet();animSet.setDuration(durationTime);animSet.playTogether(anim1, anim2);animSet.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {//Log.v(TAG, "ll_now动画开始前位置:"+ll_now.getX()+"*"+ll_now.getY());//Log.v(TAG, "ll_down动画开始前位置:"+ll_down.getX()+"*"+ll_down.getY());}@Overridepublic void onAnimationEnd(Animator animation) {//滚动结束后,now的位置变成了-scrollHeight,这时将他移动到最底下ll_now.setY(scrollHeight);//down的位置变为0,也就是当前看见的ll_down.setY(0);//Log.v(TAG, "1调整之后ll_now位置:"+ll_now.getX()+"*"+ll_now.getY());//Log.v(TAG, "1调整之后ll_down位置:"+ll_down.getX()+"*"+ll_down.getY());LinearLayout temp = ll_now;ll_now = ll_down;ll_down = temp;//Log.v(TAG, "2调整之后ll_now位置:"+ll_now.getX()+"*"+ll_now.getY());//Log.v(TAG, "2调整之后ll_down位置:"+ll_down.getX()+"*"+ll_down.getY());//给不可见的控件绑定新数据boundData(false);handler.removeMessages(MSG_SCROL);if(isCancel) {return;}handler.sendEmptyMessageDelayed(MSG_SCROL, periodTime);}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});animSet.start();}/*** 向容器中添加子条目* @param first*/private void boundData(boolean first){if(adapter==null || adapter.getCount()<=0)return;if(first){//第一次绑定数据,需要为两个容器添加子条目boundData = true;ll_now.removeAllViews();for(int i = 0; i<limit; i++){if(dataIndex>=adapter.getCount())dataIndex = 0;View view = adapter.getView(dataIndex);//设置点击监听view.setClickable(true);view.setOnClickListener(this);ll_now.addView(view);dataIndex ++;}}//每次动画结束之后,为预备容器添加新条目ll_down.removeAllViews();for(int i = 0; i<limit; i++){if(dataIndex>=adapter.getCount())dataIndex = 0;View view = adapter.getView(dataIndex);//设置点击监听view.setClickable(true);view.setOnClickListener(this);ll_down.addView(view);dataIndex ++;}}@Overridepublic void onClick(View v) {if(clickListener!=null){Object obj = v.getTag();clickListener.onItemClick(obj);}}public interface LimitScrollAdapter{public int getCount();public View getView(int index);}private LimitScrollAdapter adapter;interface OnItemClickListener{public void onItemClick(Object obj);}private OnItemClickListener clickListener;/**********************public API 以下为暴露的接口***********************//*** 1、设置数据适配器* @param adapter*/public void setDataAdapter(LimitScrollAdapter adapter){this.adapter = adapter;handler.sendEmptyMessage(MSG_SETDATA);}/*** 2、开始滚动* 应该在两处调用此方法:* ①、Activity.onStart()* ②、MyLimitScrllAdapter.setDatas()*/public void startScroll(){if(adapter==null||adapter.getCount()<=0)return;if(!boundData){handler.sendEmptyMessage(MSG_SETDATA);}isCancel = false;Log.e(TAG, "开始滚动");handler.removeMessages(MSG_SCROL); //先清空所有滚动消息,避免滚动错乱handler.sendEmptyMessageDelayed(MSG_SCROL, periodTime);}/*** 3、停止滚动* 当在Activity不可见时,在Activity.onStop()中调用*/public void cancel(){isCancel = true;Log.e(TAG, "停止滚动");}/*** 4、设置条目点击事件* @param listener*/public void setOnItemClickListener(OnItemClickListener listener){this.clickListener = listener;}}

Step2:limit_scroller.xml(设置了2组数据的滚动)——自定义LimitScrollerView的布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><LinearLayout android:id="@+id/ll_content1"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"/><LinearLayout android:id="@+id/ll_content2"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"/></LinearLayout>

Step3:自定义适配器 MyLimitScrollAdapter

public class MyLimitScrollAdapter implements LimitScrollerView.LimitScrollAdapter{private Context context;private List<String> mLists;public MyLimitScrollAdapter(List<String> mLists, Context context) {this.context = context;this.mLists = mLists;}@Overridepublic int getCount() {return mLists == null ? 0:mLists.size();}@Overridepublic View getView(int index) {View itemView = LayoutInflater.from(context).inflate(R.layout.limit_scroller_item, null, false);TextView tv_text = (TextView)itemView.findViewById(R.id.tv_text);tv_text.setText(mLists.get(index));return itemView;}}

Step4:limit_scroller_item.xml(适配器的布局)——自定义MyLimitScrollAdapter的布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical"><ImageView android:visibility="gone"android:id="@+id/iv_icon"android:layout_width="30dip"android:layout_height="30dip"android:src="@mipmap/ic_launcher"/><TextView android:id="@+id/tv_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dip"android:paddingTop="3dip"android:paddingBottom="3dip"android:textColor="@color/black"android:text="asdfasdfasdf"/></LinearLayout>

Step5:values/attrs.xml的代码

<resources><declare-styleable name="LimitScroller"><!--显示的条目数量--><attr name="limit" format="integer" /><!--滚动速度,比如3000,滚动时间会持续3秒钟--><attr name="durationTime" format="integer" /><!--滚动间隔,比如5000,滚动完成后停留5秒继续滚动--><attr name="periodTime" format="integer" /></declare-styleable></resources>

Step6:主布局中放入该布局(注意修改下面布局中LimitScrollerView的位置,不然会报错的(这里我们放在View的文件夹下))

<LinearLayout android:orientation="horizontal"android:layout_gravity="center"android:layout_width="match_parent"android:layout_height="wrap_content"><views.LimitScrollerView android:id="@+id/limitScroll"android:layout_width="match_parent"android:layout_height="wrap_content"openxu:limit="2"openxu:durationTime="1200"openxu:periodTime="2000"/></LinearLayout>

Step7:数据初始化

private Date d1,d2;private long diff,year,days,hours,minutes,second;private String mDiffTime="";//广告条与服务时间差private String mTitle="";//广告条显示内容private MyLimitScrollAdapter myScrollAdapter;private List<String> info = new ArrayList<>();//广告条集合private List<String> mNoticeTitle = new ArrayList<>();//公告private List<String> mNoticeTime = new ArrayList<>();//公告时间//这里的数据是假数据,如果请求接口了,记得每次add数据前先清空以上的集合for(int i=0;i<10;i++){mNoticeTitle.add("公告"+i);mNoticeTime.add("-04-11 17:26:10");}

由于实际需求的满足,我们的广告头部需要插入时间的判断,显示最新的几条数据(这里数据库第一次调用接口会返回一个serviceTime,即d1.getTime()),代码如下:

if (null!=info || info.size()>0){info.clear();initSetTimeData();}else {initSetTimeData();}//时间计算private void initSetTimeData() {if (mNoticeTitle.size()>0){for (int i = 0; i < mNoticeTitle.size(); i++) {try {d1 = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).parse(mNoticeTime.get(i));d2 = new Date(System.currentTimeMillis());//获取当前系统时间diff = d2.getTime() - d1.getTime();//时间差year = diff / (1000 * 60 * 60 * 24 * 365);days = diff / (1000 * 60 * 60 * 24);hours = (diff-days*(1000 * 60 * 60 * 24))/(1000* 60 * 60);minutes = (diff-days*(1000 * 60 * 60 * 24)-hours*(1000* 60 * 60))/(1000* 60);second = (diff-days*(1000 * 60 * 60 * 24)-hours*(1000* 60 * 60)-minutes*(1000* 60))/(1000);if (year>=1){mDiffTime = year +"年前";}else if (days>=1){mDiffTime = days +"天前";}else if (hours>=1 && hours<=24){mDiffTime =hours+"小时前";}else if (minutes>=1 && minutes<=60){mDiffTime = minutes+"分钟前";}else if (second>0 && second<=60){mDiffTime = second+"秒前";}else {mDiffTime = "";}} catch (ParseException e) {e.printStackTrace();}if (null!= mNoticeTitle.get(i)){mTitle = mNoticeTitle.get(i);}else {mTitle = "";}info.add(mDiffTime + " "+ mTitle);}}else {info.add(mDiffTime + " "+ mTitle);}}//到这里,我们的info集合中数据已有,那么开始把数据匹配到adapter中myScrollAdapter = new MyLimitScrollAdapter(info,getActivity());limitScroll.setDataAdapter(myScrollAdapter);limitScroll.startScroll(); //开始滚动//到这里,基本功能已完成,但是,我们还需要做的是控制它的滚动效果,所以。。。@Overridepublic void onStart() {super.onStart();limitScroll.startScroll(); //开始滚动}@Overridepublic void onStop() {super.onStop();limitScroll.cancel();//停止滚动}

最后祝你顺利完成!
参考网址:/openXu/LimitScrollerView

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。