1. 程式人生 > >自定義Adapter實現RecyclerView的可展開二級列表expand效果

自定義Adapter實現RecyclerView的可展開二級列表expand效果

網上實現可展開效果的RecyclerView做法很多,但轉發黨太多,幾乎找不到比較符合效率的做法,其中坑也不少。

想著RecyclerView這麼強大,決定自己研究一個,並基於以下四個原則:

1、作為一個有強迫症的人,我只想僅用一個RecyclerView搞定這個效果,不想任何RecyclerView巢狀GridView或者ListView之類的想著就蛋疼的做法,程式碼也不優美。

2、RecyclerView顯示什麼,它的資料列表應該也跟顯示一致,這樣比較好維護。

3、對item或者是item裡面的一些控制元件的點選處理,希望能在Activity中監聽處理,Adapter中僅對資料進行顯示處理,不涉及複雜的修改資料資訊的操作。

4、設想是通過RecyclerView的新增和移除item方式來做展開和收起的效果,這樣可以利用RecyclerVIewinsert和remove item的動畫效果,還可以自由設定展開的資訊類似GridView的形式。大致效果如下圖:


正式開始實現這個效果:

一、首先定義資料類CourseInfo、ChapterInfo、SectionInfo,這三個都繼承同一個BaseInfo。

public class CourseInfo extends BaseInfo {
    public int id;
    public String name;

    public List<ChapterInfo> chapterInfos = new ArrayList<>();
}

public class ChapterInfo extends BaseInfo {

    public String name;
    public int chapterIndex;

    public List<SectionInfo> sectionInfos = new ArrayList<>();
}

public class SectionInfo extends BaseInfo {

    public String name;
    public int chapterIndex;
    public int sectionIndex;
}

public class BaseInfo implements Serializable {
}

二、重要的adapter實現方法如下:

之所以前面的資料型別都繼承同一個BaseInfo,就是為了能把不同資料都裝進一個list中。

原理主要是傳入的資料和顯示的資料分開,維護一個顯示資料列表,展開就新增item,收起就移除item,這樣新增和移除都可以利用RecyclerView自身的動畫效果。

當然,如果想更改動畫效果貌似還可以自定義自己的ItemAnimator,這個有空可以研究研究。

package com.ldw.testwork.expandrecyclerview;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.ldw.testwork.R;

import java.util.ArrayList;
import java.util.List;

import timber.log.Timber;

/**
 * 一個可展開和收起的RecyclerView資料處理,傳進的資料和顯示的資料分開,展開新增item,收起則刪除item。
 * Created by ldw on 2017/12/1.
 */

public class ChapterAdapter extends RecyclerView.Adapter implements View.OnClickListener {

    public static final int VIEW_TYPE_CHAPTER = 1;
    public static final int VIEW_TYPE_SECTION = 2;

    //傳進來的課程資訊
    private CourseInfo courseInfo;

    //顯示的資料集
    private List<BaseInfo> dataInfos = new ArrayList<>();
    //當前展開的課時,-1代表沒有任何展開
    private int curExpandChapterIndex = -1;

    public ChapterAdapter(CourseInfo _courseInfo) {
        this.courseInfo = _courseInfo;
        for(BaseInfo info : courseInfo.chapterInfos){
            dataInfos.add(info);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView;
        if(viewType == VIEW_TYPE_CHAPTER){
            itemView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_chapter, parent, false);
            return new ItemHolder(itemView);
        }else{
            itemView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_section, parent, false);
            return new ItemSectionHolder(itemView);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        //Timber.v("---onBindViewHolder---position = "+position);
        if(getItemViewType(position) == VIEW_TYPE_CHAPTER){
            ItemHolder itemHolder = (ItemHolder) holder;
            itemHolder.itemView.setTag(position);
            itemHolder.tvPractise.setTag(position);

            ChapterInfo chapterInfo = (ChapterInfo) dataInfos.get(position);
            itemHolder.tvName.setText(chapterInfo.name);

            if(chapterInfo.sectionInfos.size() > 0){
                itemHolder.ivArrow.setVisibility(View.VISIBLE);
                if(curExpandChapterIndex == position){
                    itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_up);
                }else{
                    itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_down);
                }
            }else{
                itemHolder.ivArrow.setVisibility(View.INVISIBLE);
            }

        }else{
            ItemSectionHolder itemSectionHolder = (ItemSectionHolder) holder;
            itemSectionHolder.tvName.setTag(position);

            SectionInfo sectionInfo = (SectionInfo) dataInfos.get(position);
            itemSectionHolder.tvName.setText(sectionInfo.name);
        }
    }

    //該方法只更改itemView的部分資訊,不全部重新整理
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
        //Timber.v("---onBindViewHolder---payloads = "+payloads + ", "+position);
        if(payloads.isEmpty()){
            super.onBindViewHolder(holder, position, payloads);
        }else{
            String str = (String) payloads.get(0);
            //更改view的tag
            if(str.equals("change_position")){
                if(getItemViewType(position) == VIEW_TYPE_CHAPTER){
                    ItemHolder itemHolder = (ItemHolder) holder;
                    itemHolder.itemView.setTag(position);
                    itemHolder.tvPractise.setTag(position);
                    //改變箭頭方向
                    if(curExpandChapterIndex == position){
                        itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_up);
                    }else{
                        itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_down);
                    }
                }else{
                    ItemSectionHolder itemSectionHolder = (ItemSectionHolder) holder;
                    itemSectionHolder.tvName.setTag(position);
                }
            }
        }
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public int getItemCount() {
        if(dataInfos == null){
            return 0;
        }else{
            return dataInfos.size();
        }
    }

    @Override
    public int getItemViewType(int position) {
        if(dataInfos.get(position) instanceof ChapterInfo){
            return VIEW_TYPE_CHAPTER;
        }else if(dataInfos.get(position) instanceof SectionInfo){
            return VIEW_TYPE_SECTION;
        }
        return super.getItemViewType(position);
    }

    public class ItemHolder extends RecyclerView.ViewHolder {
        public LinearLayout llBg;
        public ImageView ivArrow;
        public TextView tvName;
        public TextView tvPractise;
        public LinearLayout llSection;
        public GridView gvSection;

        public ItemHolder(View itemView) {
            super(itemView);
            ivArrow = (ImageView) itemView.findViewById(R.id.iv_item_chapter_arrow);
            tvName = (TextView) itemView.findViewById(R.id.tv_item_chapter_name);
            tvPractise = (TextView) itemView.findViewById(R.id.tv_item_chapter_practise);

            //將建立的View註冊點選事件
            itemView.setOnClickListener(ChapterAdapter.this);
            tvPractise.setOnClickListener(ChapterAdapter.this);
        }
    }

    public class ItemSectionHolder extends RecyclerView.ViewHolder {
        public TextView tvName;

        public ItemSectionHolder(View itemView) {
            super(itemView);
            tvName = (TextView) itemView.findViewById(R.id.tv_item_section_name);

            //將建立的View註冊點選事件
            tvName.setOnClickListener(ChapterAdapter.this);
        }
    }


    ////////////////////////////以下為item點選處理///////////////////////////////

    private OnRecyclerViewItemClickListener mOnItemClickListener = null;

    public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
        this.mOnItemClickListener = listener;
    }

    /** item裡面有多個控制元件可以點選 */
    public enum ViewName {
        CHAPTER_ITEM,
        CHAPTER_ITEM_PRACTISE,
        SECTION_ITEM
    }

    public interface OnRecyclerViewItemClickListener {
        void onClick(View view, ViewName viewName, int chapterIndex, int sectionIndex);
    }

    @Override
    public void onClick(View v) {
        if (mOnItemClickListener != null) {
            //注意這裡使用getTag方法獲取資料
            int position = (int) v.getTag();
            ViewName viewName = ViewName.CHAPTER_ITEM;
            int chapterIndex = -1;
            int sectionIndex = -1;
            if(getItemViewType(position) == VIEW_TYPE_CHAPTER){
                ChapterInfo chapterInfo = (ChapterInfo) dataInfos.get(position);
                chapterIndex = chapterInfo.chapterIndex;
                sectionIndex = -1;
                if(v.getId() == R.id.tv_item_chapter_practise){
                    viewName = ViewName.CHAPTER_ITEM_PRACTISE;
                }else{
                    viewName = ViewName.CHAPTER_ITEM;
                    if(chapterInfo.sectionInfos.size() > 0){
                        if(chapterIndex == curExpandChapterIndex){
                            narrow(curExpandChapterIndex);
                        }else{
                            narrow(curExpandChapterIndex);
                            expand(chapterIndex);
                        }
                    }
                }
            }else if(getItemViewType(position) == VIEW_TYPE_SECTION){
                SectionInfo sectionInfo = (SectionInfo) dataInfos.get(position);
                viewName = ViewName.SECTION_ITEM;
                chapterIndex = sectionInfo.chapterIndex;
                sectionIndex = sectionInfo.sectionIndex;
            }
            mOnItemClickListener.onClick(v, viewName, chapterIndex, sectionIndex);
        }
    }

    /**
     * 展開某個item
     * @param chapterIndex
     */
    private void expand(int chapterIndex){
        dataInfos.addAll(chapterIndex+1, courseInfo.chapterInfos.get(chapterIndex).sectionInfos);
        curExpandChapterIndex = chapterIndex;
        Timber.v("---expand---"+(chapterIndex+1)+", "+courseInfo.chapterInfos.get(chapterIndex).sectionInfos.size());
        notifyItemRangeInserted(chapterIndex+1, courseInfo.chapterInfos.get(chapterIndex).sectionInfos.size());

        /*notifyItemRangeChanged(chapterIndex + 1 + courseInfo.chapterInfos.get(chapterIndex).sectionInfos.size(),
                getItemCount() - chapterIndex - 1, "change_position");*/
        notifyItemRangeChanged(0, getItemCount(), "change_position");
    }

    /**
     * 收起某個item
     * @param chapterIndex
     */
    private void narrow(int chapterIndex){
        if(chapterIndex != -1){
            int removeStart = chapterIndex + 1;
            int removeCount = 0;
            for(int i=removeStart; i<dataInfos.size() && getItemViewType(i) == VIEW_TYPE_SECTION; i++){
                removeCount++;
            }
            dataInfos.removeAll(courseInfo.chapterInfos.get(chapterIndex).sectionInfos);
            curExpandChapterIndex = -1;
            Timber.v("---narrow---"+removeStart+", "+removeCount);
            notifyItemRangeRemoved(removeStart, removeCount);

            //notifyItemRangeChanged(removeStart, getItemCount() - removeStart, "change_position");
            notifyItemRangeChanged(0, getItemCount(), "change_position");
        }
    }

}

三、佈局檔案,activity只是一個RecyclerView,這裡不貼出來了。

以下是item_chapter.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="50dp"
    android:background="#eff7e9"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/iv_item_chapter_arrow"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_gravity="center_vertical"
        android:background="@drawable/arrow_down"/>
    <TextView
        android:id="@+id/tv_item_chapter_name"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:textSize="12sp"
        android:paddingStart="5dp"
        android:paddingEnd="30dp"
        android:maxLines="1"
        android:ellipsize="end"
        android:gravity="start|center_vertical"
        android:text="item_chapter_name"/>
    <TextView
        android:id="@+id/tv_item_chapter_practise"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:layout_gravity="center_vertical"
        android:gravity="center"
        android:text="practise"/>

</LinearLayout>
以下是item_section.xml檔案:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="35dp"
    android:layout_gravity="center"
    android:gravity="center">

    <TextView
        android:id="@+id/tv_item_section_name"
        android:layout_width="80dp"
        android:layout_height="35dp"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="section"/>

</RelativeLayout>

四、Activity中的程式碼:

package com.ldw.testwork;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.ldw.testwork.expandrecyclerview.ChapterAdapter;
import com.ldw.testwork.expandrecyclerview.ChapterInfo;
import com.ldw.testwork.expandrecyclerview.CourseInfo;
import com.ldw.testwork.expandrecyclerview.SectionInfo;
import com.ldw.testwork.utils.ToastUtil;

import timber.log.Timber;

public class ExpandRecyclerViewActivity extends AppCompatActivity {

    RecyclerView mRecyclerView;
    CourseInfo mCourseInfo;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_expandrecyclerview);

        initData();
        initViews();

    }

    private void initData(){
        //假資料
        mCourseInfo = new CourseInfo();
        mCourseInfo.name = "假裝是課程的名稱";
        for(int i=0; i<31; i++){
            ChapterInfo chapterInfo = new ChapterInfo();
            chapterInfo.name = "假裝是課時名稱"+(i+1);
            chapterInfo.chapterIndex = i;
            if(i==0){
                for(int j=0; j<2; j++){
                    SectionInfo sectionInfo = new SectionInfo();
                    sectionInfo.name = "第"+(j+1)+"節";
                    sectionInfo.chapterIndex = i;
                    sectionInfo.sectionIndex = j;
                    chapterInfo.sectionInfos.add(sectionInfo);
                }
            }else if(i==1){
                for(int j=0; j<3; j++){
                    SectionInfo sectionInfo = new SectionInfo();
                    sectionInfo.name = "第"+(j+1)+"節";
                    sectionInfo.chapterIndex = i;
                    sectionInfo.sectionIndex = j;
                    chapterInfo.sectionInfos.add(sectionInfo);
                }
            }else if(i==2){

            }else{
                for (int j = 0; j < 4; j++) {
                    SectionInfo sectionInfo = new SectionInfo();
                    sectionInfo.name = "第" + (j + 1) + "節";
                    sectionInfo.chapterIndex = i;
                    sectionInfo.sectionIndex = j;
                    chapterInfo.sectionInfos.add(sectionInfo);
                }
            }
            mCourseInfo.chapterInfos.add(chapterInfo);
        }
    }

    private void initViews(){
        mRecyclerView = (RecyclerView) findViewById(R.id.rv_expand);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        final ChapterAdapter chapterAdapter = new ChapterAdapter(mCourseInfo);
        mRecyclerView.setAdapter(chapterAdapter);
        chapterAdapter.setOnItemClickListener(new ChapterAdapter.OnRecyclerViewItemClickListener() {
            @Override
            public void onClick(View view, ChapterAdapter.ViewName viewName, int chapterIndex, int sectionIndex) {
                //Timber.v("---onClick---"+viewName+", "+chapterIndex+", "+sectionIndex);
                switch (viewName){
                    case CHAPTER_ITEM:
                        if(mCourseInfo.chapterInfos.get(chapterIndex).sectionInfos.size() > 0){
                            Timber.v("---onClick---just expand or narrow: "+chapterIndex);
                            if(chapterIndex + 1 == mCourseInfo.chapterInfos.size()){
                                //如果是最後一個,則滾動到展開的最後一個item
                                mRecyclerView.smoothScrollToPosition(chapterAdapter.getItemCount());
                                Timber.v("---onClick---scroll to bottom");
                            }
                        }else{
                            onClickChapter(chapterIndex);
                        }
                        break;
                    case CHAPTER_ITEM_PRACTISE:
                        onClickPractise(chapterIndex);
                        break;
                    case SECTION_ITEM:
                        onClickSection(chapterIndex, sectionIndex);
                        break;
                }
            }
        });

        //以下是對佈局進行控制,讓課時佔據一行,小節每四個佔據一行,結果就是相當於一個ListView巢狀GridView的效果。
        final GridLayoutManager manager = new GridLayoutManager(this, 4);
        manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return chapterAdapter.getItemViewType(position) == ChapterAdapter.VIEW_TYPE_CHAPTER ? 4 : 1;
            }
        });
        mRecyclerView.setLayoutManager(manager);
    }
    
    private void onClickChapter(int chapterIndex){
        Timber.v("---onClick---play chapter: "+chapterIndex);
        ToastUtil.showToast(ExpandRecyclerViewActivity.this, "播放"+chapterIndex);
    }
    
    private void onClickSection(int chapterIndex, int sectionIndex){
        Timber.v("---onClick---play---section: "+chapterIndex+", "+sectionIndex);
        ToastUtil.showToast(ExpandRecyclerViewActivity.this, "播放"+chapterIndex+", "+sectionIndex);
    }
    
    private void onClickPractise(int chapterIndex){
        Timber.v("---onClick---practise: "+chapterIndex);
    }

}

五、程式碼看似簡單,其中adapter有幾個坑:

1、插入或者移除後其他的item中的position不會變(此position不僅是指我設定的tag那個position),比如原來有4個item,position為0 1 2 3,中間插入兩個item後,position變為0 1 2 3 2 3,非常奇葩,會出現各種問題,當然如果呼叫notifyDataSetChanged();進行重新整理,那不會有什麼問題,只是RecyclerView的新增移除item動畫效果就沒了。網上解決方法千篇一律,重點是還達不到效果。個人的解決方法在2中說明。

2、解決之前,先說明另一個方法public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads),看到沒有,它筆平常使用的多了一個引數payloads,經過了解,它的用途是可以在不用重新整理整個item,而對部分item中的某些資訊進行修改。我們正好利用這個特點,前面說position不會自動更新,那好,我們就呼叫這個方法notifyItemRangeChanged(0, getItemCount(), "change_position");讓它去更新item,這樣既不會重新重新整理整個item,又能保證position的正確性。這樣就完美解決事情了。


六、總結,RecyclerView有以下幾個好東西:

ItemDecoration :設定item間隔

GridLayoutManager :設定顯示佈局,比如上面例子中,哪個item佔據一行,哪個item多個佔據一行,可以方便實現GridView效果。

itemAnimator:設定item新增和移除等的各種效果。

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads):改方法最後一個引數可以設定不同值,例如notifyItemRangeChanged(0, getItemCount(), "change_position");,從而在更新中能只對item中某個資訊進行修改,而不用整個item重新整理。

原創文章,轉載請註明出處:http://blog.csdn.net/lin_dianwei/article/details/78725014

相關推薦

定義Adapter實現RecyclerView展開二級列表expand效果

網上實現可展開效果的RecyclerView做法很多,但轉發黨太多,幾乎找不到比較符合效率的做法,其中坑也不少。 想著RecyclerView這麼強大,決定自己研究一個,並基於以下四個原則: 1、作為一個有強迫症的人,我只想僅用一個RecyclerView搞定這個效果,不想

AutoCompleteTextView與定義Adapter實現自動補全

AutoCompleteTextView繼承自TextView,是一種可以實現自動補全的功能的TextView。先看效果: 該功能在目前很常見,例如在使用者進行登入的時候或者註冊的時候都用到了這種功能,在Android中這樣的效果我們可以藉助AutoCo

定義Adapter實現多檢視Item的ListView

From http://www.devdiv.com/adapter_item_listview-article-3730-1.html 1、原理分析         Adapter對於ListView是非常重要的,它處於listView和資料來源的中間,負責為L

Android中Spinner下拉列表(使用ArrayAdapter和定義Adapter實現

     今天學習了Spinner元件,使用Spinner相當於從下拉列表中選擇專案,下面演示一下Spinner的使用(分別使用ArrayAdapter和自定義Adapter實現) (一):使用Arr

定義ViewGroup實現多個單頁面上下滑動效果

閱讀過自定義ViewGroup實現仿淘寶的商品詳情頁的童鞋,應該都瞭解了ViewGroup中onMeasure、onLayout、onTouchEvent等相關方法的使用。在介紹仿淘寶商品詳情頁時,我們提到過現在網上很多實現方法是使用ScrollView巢狀兩個

復用的定義Adapter

text logs 存儲 group bili this resource list extend public abstract class MyAdapter<T> extends BaseAdapter { private ArrayList&

利用原生js實現定義滾動條(點選到達,拖動到達)

1.HTML檔案 div1是滾動條,div2是滾動小球,div3是文字區域容器,div4是文字區域。 <div id="div"> <div id="div1"> <div id="div2"> </div> </div&

RecyclerView定義LayoutManager實現橫向瀑布流

最近由於公司專案需要,做了一個橫向瀑布流的元件,如下圖;這個元件是通過自定義LayoutManager實現,LayoutManager為我們提供了強大的自定義功能,但是實現過程卻不簡單,搗鼓了兩天,也就算基本可以用了;Demo原始碼在最下面,這裡主要記錄一些自定義Layou

Android 定義Adapter實現定義填充ListView的Item

1>實體類---即ListView中各個Item中填充的內容 package com.demo.cxc.compoundview.com.demo.cxc.entity; import java.text.SimpleDateFormat; import java.

TextView使用定義HtmlHttpImageGetter實現非同步載入網路圖片,限制載入圖片數量

import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import androi

Spring+SpringMVC+Mybatis 利用AOP定義註解實現配置日

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 5

Android -- 定義view實現keep歡迎頁倒計時效果

super onfinish -m use new getc awt ttr alt 1,最近打開keep的app的時候,發現它的歡迎頁面的倒計時效果還不錯,所以打算自己來寫寫,然後就有了這篇文章。 2,還是老規矩,先看一下我們今天實現的效果   相較於我們常見的倒計時

Android定義View——實現水波紋效果類似剩余流量球

string 三個點 pre ber block span 初始化 move 理解 最近突然手癢就想搞個貝塞爾曲線做個水波紋效果玩玩,終於功夫不負有心人最後實現了想要的效果,一起來看下吧: 效果圖鎮樓 一:先一步一步來分解一下實現的過程 需要繪制一個正弦曲線(sin

Android定義processor實現bindView功能

lis dds 定義 java代碼 cli 註冊 文章 type() mage 一、簡介 在現階段的Android開發中,註解越來越流行起來,比如ButterKnife,Retrofit,Dragger,EventBus等等都選擇使用註解來配置。按照處理時期,註解又分為兩

定義toast實現

web javascript html5 toast ys_toast.css.ys-toast{ position:fixed; left:0; right:0; top:0; bottom:0; z-index: 999999; } .ys-toast>em{ pos

SpringVC 攔截器+定義註解 實現權限攔截

json.js 加載 bean media tar attr esp 權限 encoding 1.springmvc配置文件中配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w

定義ScrollView 實現上拉下拉的回彈效果--並且子控件中有Viewpager的情況

是否 AS abs pri tar utils lda animation ted onInterceptTouchEvent就是對子控件中Viewpager的處理:左右滑動應該讓viewpager消費 1 public class MyScrollView ext

[python]RobotFramework定義實現UI自動化

bubuko output source 自動 封裝 9.png 全局變量 詳細 變量 1.安裝教程 環境搭建不多說,網上資料一大堆,可參考https://www.cnblogs.com/puresoul/p/3854963.html,寫的比較詳細,值得推薦。目前pyt

NPOI+反射+定義特性實現上傳excel轉List及驗證

type set custom pre script private property xssf don 1.自定義特性 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited

Android bc信用盤搭建定義behavior 實現上滑 隱藏底部view

退出 Y軸 log rect app sum string dsl oss 布局 <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent"