ViewFlipper实现滚动布局

来源:互联网 发布:sql server 导出数据库 编辑:程序博客网 时间:2024/06/08 18:28

今天在git上面看到了一个 仿淘宝头条的滚动效果,就看了下源码                

大家要养成看源码的好习惯,才能更好的理解

       然后自己照着写了下  然后顺便看了下 ViewFlipper 这个类

先上效果图吧:                               最后分析的有 系统的这个类中的方法 的执行 (大神略过,菜鸟请贴完代码后往下看.....)



自定义类继承 ViewFlipper

package com.dreamlive.upmarqueeview;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.animation.Animation;import android.view.animation.AnimationUtils;import android.widget.ViewFlipper;import java.util.List;/** * 仿淘宝首页的 淘宝头条滚动的自定义View * * Created by xi on 2016/11/28. */public class UPMarqueeView extends ViewFlipper {    private Context mContext;    private boolean isSetAnimDuration = false;    /**     * 要多久 烙下一个视图  不搞的话 默认是3000     *  意思就是  切换的间隔     */    private int interval = 2000;    /**     * 动画时间     *  因为设置的是 进入 和 出去 的动画为  透明 和 位移     */    private int animDuration = 500;    public UPMarqueeView(Context context, AttributeSet attrs) {        super(context, attrs);        init(context, attrs, 0);    }    private void init(Context context, AttributeSet attrs, int defStyleAttr) {        this.mContext = context;        // 设置区间间隔        setFlipInterval(interval);        Animation animIn = AnimationUtils.loadAnimation(mContext, R.anim.anim_marquee_in);        if (isSetAnimDuration) animIn.setDuration(animDuration);        setInAnimation(animIn);        Animation animOut = AnimationUtils.loadAnimation(mContext, R.anim.anim_marquee_out);        if (isSetAnimDuration) animOut.setDuration(animDuration);        setOutAnimation(animOut);    }    /**     * 设置循环滚动的View数组     *     * @param views     */    public void setViews(final List<View> views) {        if (views == null || views.size() == 0) return;        removeAllViews();        for ( int i = 0; i < views.size(); i++) {            final int position=i;            //设置监听回调            views.get(i).setOnClickListener(new OnClickListener() {                @Override                public void onClick(View v) {                    if (onItemClickListener != null) {                        onItemClickListener.onItemClick(position, views.get(position));                    }                }            });            addView(views.get(i));        }        startFlipping();    }    /**     * 设置动画时长  默认为500ms     * @param ms     */    public void setAnimDuration(int ms){        this.animDuration = ms;    }    /**     *  设置间隔时长区间  这里默认给的2s  系统源码里面默认给的3s     * @param interval     */    public void setInterval(int interval) {        this.interval = interval;    }    /**     * 点击     */    private OnItemClickListener onItemClickListener;    /**     * 设置监听接口     * @param onItemClickListener     */    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {        this.onItemClickListener = onItemClickListener;    }    /**     * item_view的接口     */    public interface OnItemClickListener {        void onItemClick(int position, View view);    }}

使用:

1.xml 布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="horizontal">    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="80dp">        <TextView            android:id="@+id/tbtv"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_margin="10dp"            android:layout_centerVertical="true"            android:textSize="22sp"            android:textColor="@color/red"            android:text="@string/taobao" />        <com.dreamlive.upmarqueeview.UPMarqueeView            android:id="@+id/upview1"            android:layout_marginLeft="20dp"            android:layout_width="match_parent"            android:layout_toRightOf="@+id/tbtv"            android:layout_centerVertical="true"            android:layout_marginTop="10dp"            android:layout_height="match_parent"></com.dreamlive.upmarqueeview.UPMarqueeView>    </RelativeLayout></LinearLayout>

item_view:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center"    android:orientation="vertical">    <RelativeLayout        android:id="@+id/rl"        android:layout_width="match_parent"        android:layout_height="wrap_content">        <TextView            android:id="@+id/title_tv1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="热议"            android:textSize="9sp"            android:padding="3dp"            android:background="@drawable/textview_border"            android:layout_marginRight="6dp"            android:textColor="@color/red" />        <TextView            android:id="@+id/tv1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_toRightOf="@+id/title_tv1"            android:ellipsize="end"            android:textSize="14sp"            android:maxLines="1" />    </RelativeLayout>    <RelativeLayout        android:id="@+id/rl2"        android:layout_width="match_parent"        android:layout_marginTop="5dp"        android:layout_height="wrap_content">        <TextView            android:id="@+id/title_tv2"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="热评"            android:padding="3dp"            android:textSize="9sp"            android:background="@drawable/textview_border"            android:layout_marginRight="6dp"            android:textColor="@color/red" />        <TextView            android:id="@+id/tv2"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_toRightOf="@+id/title_tv2"            android:ellipsize="end"            android:textSize="14sp"            android:maxLines="1" />    </RelativeLayout></LinearLayout>


2.代码

package com.dreamlive.upmarqueeviewdemo;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.LayoutInflater;import android.view.View;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;import com.dreamlive.upmarqueeview.UPMarqueeView;import java.util.ArrayList;import java.util.List;/** * 仿淘宝首页的 淘宝头条滚动的自定义View * Created by xi on 2016/11/28. */public class MainActivity extends AppCompatActivity {    private UPMarqueeView upview1;    List<String> data = new ArrayList<>();    List<View> views = new ArrayList<>();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initParam();        initdata();        initView();    }    /**     * 实例化控件     */    private void initParam() {        upview1 = (UPMarqueeView) findViewById(R.id.upview1);    }    /**     * 初始化界面程序     */    private void initView() {        setView();        upview1.setViews(views);        /**         * 设置item_view的监听         */        upview1.setOnItemClickListener(new UPMarqueeView.OnItemClickListener() {            @Override            public void onItemClick(int position, View view) {                Toast.makeText(MainActivity.this, "你点击了第几个items" + position, Toast.LENGTH_SHORT).show();            }        });    }    /**     * 初始化需要循环的View     * 为了灵活的使用滚动的View,所以把滚动的内容让用户自定义     * 假如滚动的是三条或者一条,或者是其他,只需要把对应的布局,和这个方法稍微改改就可以了,     */    private void setView() {        for (int i = 0; i < data.size(); i = i + 2) {            final int position = i;            //设置滚动的单个布局            LinearLayout moreView = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.item_view, null);            //初始化布局的控件            TextView tv1 = (TextView) moreView.findViewById(R.id.tv1);            TextView tv2 = (TextView) moreView.findViewById(R.id.tv2);            /**             * 设置监听             */            moreView.findViewById(R.id.rl).setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View view) {                    Toast.makeText(MainActivity.this, position + "你点击了" + data.get(position).toString(), Toast.LENGTH_SHORT).show();                }            });            /**             * 设置监听             */            moreView.findViewById(R.id.rl2).setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View view) {                    Toast.makeText(MainActivity.this, position + "你点击了" + data.get(position).toString(), Toast.LENGTH_SHORT).show();                }            });            //进行对控件赋值            tv1.setText(data.get(i).toString());            if (data.size() > i + 1) {                //因为淘宝那儿是两条数据,但是当数据是奇数时就不需要赋值第二个,所以加了一个判断,还应该把第二个布局给隐藏掉                tv2.setText(data.get(i + 1).toString());            } else {                moreView.findViewById(R.id.rl2).setVisibility(View.GONE);            }            //添加到循环滚动数组里面去            views.add(moreView);        }    }    /**     * 初始化数据     */    private void initdata() {        data = new ArrayList<>();        data.add("家人给2岁孩子喝这个,孩子智力倒退10岁!!!");        data.add("iPhone8最感人变化成真,必须买买买买!!!!");        data.add("简直是白菜价!日本玩家33万甩卖15万张游戏王卡");        data.add("iPhone7价格曝光了!看完感觉我的腰子有点疼...");        data.add("主人内疚逃命时没带够,回废墟狂挖30小时!");//        data.add("竟不是小米乐视!看水抢了骁龙821首发了!!!");    }}

资源文件:

in ----

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android">    <translate        android:duration="300"        android:fromYDelta="100%p"        android:toYDelta="0"/>    <alpha        android:duration="500"        android:fromAlpha="0.0"        android:toAlpha="1.0"/></set>

out----

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android">    <translate        android:duration="400"        android:fromYDelta="0"        android:toYDelta="-100%p"/>    <alpha        android:duration="500"        android:fromAlpha="1.0"        android:toAlpha="0.0"/></set>


在系统的 ViewFlipper 类中可以看到这样的代码  也就是一个广播  

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {    @Override    public void onReceive(Context context, Intent intent) {        final String action = intent.getAction();        if (Intent.ACTION_SCREEN_OFF.equals(action)) {            mUserPresent = false;            updateRunning();        } else if (Intent.ACTION_USER_PRESENT.equals(action)) {            mUserPresent = true;            updateRunning(false);        }    }};@Overrideprotected void onAttachedToWindow() {    super.onAttachedToWindow();    // Listen for broadcasts related to user-presence    final IntentFilter filter = new IntentFilter();    filter.addAction(Intent.ACTION_SCREEN_OFF);    filter.addAction(Intent.ACTION_USER_PRESENT);    // OK, this is gross but needed. This class is supported by the    // remote views machanism and as a part of that the remote views    // can be inflated by a context for another user without the app    // having interact users permission - just for loading resources.    // For exmaple, when adding widgets from a user profile to the    // home screen. Therefore, we register the receiver as the current    // user not the one the context is for.    getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),            filter, null, getHandler());// 这个方法是 Context的  注册广播的方法    if (mAutoStart) {        // Automatically start when requested        startFlipping();    }}
和registerReceiver 一样 但它是对于特殊用户使用的   也就是系统使用的

/** * @hide * Same as {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) * but for a specific user.  This receiver will receiver broadcasts that * are sent to the requested user.  It * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} * permission. * * @param receiver The BroadcastReceiver to handle the broadcast. * @param user UserHandle to send the intent to. * @param filter Selects the Intent broadcasts to be received. * @param broadcastPermission String naming a permissions that a *      broadcaster must hold in order to send an Intent to you.  If null, *      no permission is required. * @param scheduler Handler identifying the thread that will receive *      the Intent.  If null, the main thread of the process will be used. * * @return The first sticky intent found that matches <var>filter</var>, *         or null if there are none. * * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) * @see #sendBroadcast * @see #unregisterReceiver */@Nullablepublic abstract Intent registerReceiverAsUser(BroadcastReceiver receiver,        UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,        @Nullable Handler scheduler);

@Overrideprotected void onDetachedFromWindow() {    super.onDetachedFromWindow();    mVisible = false;    getContext().unregisterReceiver(mReceiver);    updateRunning();}@Overrideprotected void onWindowVisibilityChanged(int visibility) {    super.onWindowVisibilityChanged(visibility);    mVisible = visibility == VISIBLE;    updateRunning(false);}

可以去设置  切换间隔  单位为 毫秒

/** * How long to wait before flipping to the next view * * @param milliseconds *            time in milliseconds */@android.view.RemotableViewMethodpublic void setFlipInterval(int milliseconds) {    mFlipInterval = milliseconds;}

可以去启动和停止

/** * Start a timer to cycle through child views */public void startFlipping() {    mStarted = true;    updateRunning();}/** * No more flips */public void stopFlipping() {    mStarted = false;    updateRunning();}

内部用来启动和停止的方法

/** * Internal method to start or stop dispatching flip {@link Message} based * on {@link #mRunning} and {@link #mVisible} state. */private void updateRunning() {    updateRunning(true);}/** * Internal method to start or stop dispatching flip {@link Message} based * on {@link #mRunning} and {@link #mVisible} state. * * @param flipNow Determines whether or not to execute the animation now, in *            addition to queuing future flips. If omitted, defaults to *            true. */private void updateRunning(boolean flipNow) {    boolean running = mVisible && mStarted && mUserPresent;    if (running != mRunning) {        if (running) {            showOnly(mWhichChild, flipNow);            postDelayed(mFlipRunnable, mFlipInterval);        } else {            removeCallbacks(mFlipRunnable);        }        mRunning = running;    }    if (LOGD) {        Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted                + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);    }}

可以看到它调用的是  View 的延时方法     而该方法使用的还是  handler  和   队列

/** * <p>Causes the Runnable to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the user interface thread.</p> * * @param action The Runnable that will be executed. * @param delayMillis The delay (in milliseconds) until the Runnable *        will be executed. * * @return true if the Runnable was successfully placed in to the *         message queue.  Returns false on failure, usually because the *         looper processing the message queue is exiting.  Note that a *         result of true does not mean the Runnable will be processed -- *         if the looper is quit before the delivery time of the message *         occurs then the message will be dropped. * * @see #post * @see #removeCallbacks */public boolean postDelayed(Runnable action, long delayMillis) {    final AttachInfo attachInfo = mAttachInfo;    if (attachInfo != null) {        return attachInfo.mHandler.postDelayed(action, delayMillis);    }    // Postpone the runnable until we know on which thread it needs to run.    // Assume that the runnable will be successfully placed after attach.    getRunQueue().postDelayed(action, delayMillis);    return true;}

运行的队列

/** * Returns the queue of runnable for this view. * * @return the queue of runnables for this view */private HandlerActionQueue getRunQueue() {    if (mRunQueue == null) {        mRunQueue = new HandlerActionQueue();    }    return mRunQueue;}

发送的延时

public class HandlerActionQueue {    private HandlerAction[] mActions;    private int mCount;    public void post(Runnable action) {        postDelayed(action, 0);    }    public void postDelayed(Runnable action, long delayMillis) {        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);        synchronized (this) {            if (mActions == null) {                mActions = new HandlerAction[4];            }            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);            mCount++;        }    }

而系统    移除回调  是把actions 进行匹配遍历 置为null  

public void removeCallbacks(Runnable action) {    synchronized (this) {        final int count = mCount;        int j = 0;        final HandlerAction[] actions = mActions;        for (int i = 0; i < count; i++) {            if (actions[i].matches(action)) {                // Remove this action by overwriting it within                // this loop or nulling it out later.                continue;            }            if (j != i) {                // At least one previous entry was removed, so                // this one needs to move to the "new" list.                actions[j] = actions[i];            }            j++;        }        // The "new" list only has j entries.        mCount = j;        // Null out any remaining entries.        for (; j < count; j++) {            actions[j] = null;        }    }}

此外在这里 还看到了 执行的方法是用的是     handler

public void executeActions(Handler handler) {    synchronized (this) {        final HandlerAction[] actions = mActions;        for (int i = 0, count = mCount; i < count; i++) {            final HandlerAction handlerAction = actions[i];            handler.postDelayed(handlerAction.action, handlerAction.delay);        }        mActions = null;        mCount = 0;    }}


到此为止了,下班喽~~





0 0
原创粉丝点击