仿各APP搜索历史的实现(greendao+流式布局)

版权声明:出于感谢(如果有收获)或对知识的尊重,未经允许禁止转载 https://blog.csdn.net/bendan50/article/details/85170234

目录

目录

零、参考文献

一、遇到build失败(Conflict with dependency)

二、集成greendao3.2.2

1、选择greendao的说明

2、配置greendao的环境。

3、单例模式初始化

4、定义实体类

5、使用

三、流式布局,继承ViewGroup


零、参考文献

https://www.jianshu.com/p/42fb70aa1602

https://www.jb51.net/article/86703.htm

一、遇到build失败(Conflict with dependency)

android studio新建项目后,build 失败。报错如下:

'com.android.support:support-annotations' in project ':app'. Resolved versions for app (26.1.0) and test app (27.1.1) differ. 

Error:Execution failed for task ':app:preDebugAndroidTestBuild'.
> Conflict with dependency 'com.android.support:support-annotations' in project ':app'. 
Resolved versions for app (26.1.0) and test app (27.1.1) differ. 
See https://d.android.com/r/tools/test-apk-dependency-conflicts.html for details.

为依赖冲突。即app与test app的版本不同。解决方案:

在app下的build.gradle文件增加一段代码【后面会给出完整的build.gradle文件】:

//解决app与text app版本冲突Conflict with dependency 'com.android.support:support-annotations'
configurations.all {
    resolutionStrategy.force 'com.android.support:support-annotations:27.1.1'
}

二、集成greendao3.2.2

1、选择greendao的说明

android 中保存数据的方式有许多种,只考虑能不能实现的话,方案可选的如:parcelable序列化对象,然后写文件读文件;SharedPreferences使用缓存,以XML文件保存数据;android自带的SQLite数据库;ContentProvider;面向ORM(object relative mapping, 对象关系映射)的框架,如:greendao、Realm、OrmLite; 基于NoSql的ObjectBox框架。

可以选择的方案很多,关于框架的性能对比可以再次百度。面试时可以有的说。

2、配置greendao的环境。

除了百度外,最正确的方式是去Git上看(原来的我是百度,但这不是最根本的方法)。

Git地址:https://github.com/greenrobot/greenDAO

Git上有对greendao的介绍,此刻,我才知道,那些博客大神的greendao先驱者是怎么写出博客来的(当然不排除“抄”别人博客的)。

greenDAO is a light & fast ORM for Android that maps objects to SQLite databases. Being highly optimized for Android, greenDAO offers great performance and consumes minimal memory.

在介绍的下面,自然是配置方法。在此我只做个粘贴。鱼和渔都写了。

Add the following Gradle configuration to your Android project:

// In your root build.gradle file:
buildscript {
    repositories {
        jcenter()
        mavenCentral() // add repository
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.1'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
    }
}
 
// In your app projects build.gradle file:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin
 
dependencies {
    implementation 'org.greenrobot:greendao:3.2.2' // add library
}

因为是新建的工程,配置文件没什么东西,就完整的贴出来,如下:

//app build.gradle
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.crsh.test.all.ipnugreendao30"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
//解决app与text app版本冲突Conflict with dependency 'com.android.support:support-annotations'
configurations.all {
    resolutionStrategy.force 'com.android.support:support-annotations:27.1.1'
}
greendao {
    schemaVersion 1
    daoPackage 'com.crsh.test.all.ipnugreendao30.dbgen'
    targetGenDir 'src/main/java'
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    compile 'org.greenrobot:greendao:3.2.2' // add library
}
//project build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

3、单例模式初始化

/**
 * Created by crsh on 2018/12/19.
 * com.crsh.test.all.ipnugreendao30
 * com.crsh.test.all.ipnugreendao30
 */

public class MyApplication extends Application{

    private static DaoSession daoSession;
    private static MyApplication myApplication;
    //主进程名
    private static final String PROCESS_NAME = "com.crsh.test.all.ipnugreendao30";
    @Override
    public void onCreate() {
        super.onCreate();
        myApplication = this;
        //只在主进程初始化一次
        if (PROCESS_NAME.equals(CrshUtils.getCurProcessName(this))) {
            initGreenDao();
        }
    }

    private void initGreenDao() {
        try {
            DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this,"crsh-ipnu");
            Database db = helper.getWritableDb();
            daoSession = new DaoMaster(db).newSession();
        } catch (Exception e) {

        }
    }

    public static synchronized MyApplication getInstance() {
        return myApplication;
    }
    public synchronized DaoSession getDaoSession() {
        return daoSession;
    }
}

需要在AndroidManifest.xml文件中注册下。

4、定义实体类

需要在类上增加注解:@Entity

在属性上增加@NotNull或@Id或其他注解,如@Property、@Transient等。get()、set()方法会自己生成,在编译的时候。

/**
 * Created by crsh on 2018/12/19.
 * com.crsh.test.all.ipnugreendao30.bean
 * 输入历史的实体类
 */
@Entity
public class InputHistory {
    @NotNull
    @Id
    private String inputKey;        //输入的文字
    @NotNull
    private long inputTime;         //输入的时间戳

    @Generated(hash = 91787085)
    public InputHistory(@NotNull String inputKey, long inputTime) {
        this.inputKey = inputKey;
        this.inputTime = inputTime;
    }

    @Generated(hash = 1474844400)
    public InputHistory() {
    }

    public String getInputKey() {
        return inputKey;
    }

    public long getInputTime() {
        return inputTime;
    }

    public void setInputKey(String inputKey) {
        this.inputKey = inputKey;
    }

    public void setInputTime(long inputTime) {
        this.inputTime = inputTime;
    }
}

5、使用

MyApplication.getInstance().getDaoSession().insertOrReplace(new InputHistory(inputEt.getText().toString(),System.currentTimeMillis()));

List<InputHistory> historyList = MyApplication.getInstance().getDaoSession().getInputHistoryDao().queryBuilder()
                        .orderDesc(InputHistoryDao.Properties.InputTime).build().list();

MyApplication.getInstance().getDaoSession().deleteAll(InputHistory.class);

三、流式布局,继承ViewGroup

功能需求:搜索输入的文字长度是无法固定的,每一行显示几个搜索词也是不固定的,当一行显示不下时,自动增加第二行(见最终效果图)。所以需要自定义布局。

对于自定义布局,需要做几件事:1、告诉父类布局,它的布局参数;2、子控件的宽度和高亮(主要是为了适应ViewGroup布局参数。如果是固定宽高,则该步没有必要;如果是wrap_content,则该步是必须的);3、把子控件按照我们的需求摆放至父布局中,完成自定义布局。所以代码也是根据这三个步骤来的。

/**
 * Created by crsh on 2018/12/24.
 * com.crsh.test.all.ipnugreendao30
 * 自定义布局
 */

public class HistoryViewGroup extends ViewGroup{
    public HistoryViewGroup(Context context) {
        super(context);
    }

    public HistoryViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public HistoryViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //1.布局参数,确定该ViewGroup的LayoutParams
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(),attrs);
    }

    //2.测量所有子控件宽度和高度,并设置viewgroup的
    //其目的只有一个,当viewgroup设置为自适应时,计算才有意义
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //其上一级布局设置的宽高及计算模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //如果ViewGroup布局是wrap_content时,其宽高为子控件的宽高
        int width = 0;      //viewgroup的宽,如果是wrap_content的话
        int height = 0;
        int lineWidth = 0;  //一行的宽,width值是所有行宽的最大值
        int lineHeight = 0;
        int count = getChildCount();
        for (int i = 0; i < count; i++ ) {
            View childView = getChildAt(i);
            measureChild(childView,widthMeasureSpec,heightMeasureSpec);     //测量子视图
            MarginLayoutParams childLayoutParams = (MarginLayoutParams) childView.getLayoutParams();//取出布局参数,父类就是这个布局参数
            //getMeasuredWidth()获取的是view原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。
            // getWidth()获取的是这个view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小。
            int childWidth = childView.getMeasuredWidth() + childLayoutParams.leftMargin + childLayoutParams.rightMargin;
            int childHeight = childView.getMeasuredHeight() + childLayoutParams.topMargin + childLayoutParams.bottomMargin;
            //如果加入当前childView后超出最大宽度,viewgroup的宽度取最大高度,累加height,然后开启新行
            if ((lineWidth + childWidth) > widthSize) {
                width = Math.max(lineWidth,childWidth);     //加入子控件后,viewgroup的宽度需要更新
                height += lineHeight;       //开启新的一行
                lineWidth = childWidth;     //一行装不下,开启的新行,宽度自然为子控件宽度
            } else {    //没超出最大宽度,累加行宽,viewgroup的高度取行高和子控件高的最大值
                lineWidth += childWidth;
                height = Math.max(lineHeight,childHeight);  //加入子控件后,viewgroup的高度需要更新
            }
            // 如果是最后一个childView,则将当前记录的最大宽度和当前lineWidth做比较
            if (i == (count - 1)) {
                width = Math.max(lineWidth,childWidth);
                height += lineHeight;
            }
        }
        //如果是wrap_content设置为我们计算的值;否则直接设置为父容器计算的值
        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width,
                (heightMode == MeasureSpec.EXACTLY) ? heightSize : height);
    }

    //3.重写onLayout,对其所有childView进行定位(设置childView的绘制区域)
    private List<List<View>> allChildViews = new ArrayList<List<View>>();//存储所有的childView,按行记录
    private List<Integer> maxLineHeight = new ArrayList<Integer>();//存储每行的最大高度值
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        allChildViews.clear();
        maxLineHeight.clear();
        int width = getWidth();     //每行的最大宽度
        int lineWind = 0;       //即时行宽
        int lineHeight = 0;
        List<View> lineViews = new ArrayList<View>();       //每一行的子视图
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);
            MarginLayoutParams params = (MarginLayoutParams)view.getLayoutParams(); //取布局参数
            int cWidth = view.getMeasuredWidth();
            int cHeight = view.getMeasuredHeight();
            if ((lineWind + cWidth + params.leftMargin + params.rightMargin) > width) {
                maxLineHeight.add(lineHeight);      //存下该行的高
                allChildViews.add(lineViews);   //满一行了,加到总的里面
                lineViews = new ArrayList<View>();  //开启新的一行
                lineViews.add(view);    //把新的子控件添加至新的一行
                lineWind = view.getMeasuredWidth() + params.leftMargin + params.rightMargin;    //重置新一行的宽
            } else {
                lineWind += cWidth + params.leftMargin + params.rightMargin;    //更新该行的宽
                lineHeight = Math.max(lineHeight,cHeight + params.topMargin + params.bottomMargin); //更新该行的高
                lineViews.add(view);
            }
        }
        //记录最后一行
        maxLineHeight.add(lineHeight);
        allChildViews.add(lineViews);
        int left = 0;   //左坐标
        int top = 0;    //上坐标
        int lineNums = allChildViews.size();    //总行数
        for (int i = 0; i < lineNums; i++) {
            lineViews = allChildViews.get(i);   //该行能放下的所有视图
            lineHeight = maxLineHeight.get(i);  //该行的高度
            for (int j = 0; j < lineViews.size(); j++) {
                View view = lineViews.get(j);
                if (view.getVisibility() == View.GONE) {
                    continue;
                }
                MarginLayoutParams mlp = (MarginLayoutParams) view.getLayoutParams();
                view.layout(left + mlp.leftMargin,
                        top + mlp.topMargin,
                        left + mlp.leftMargin + view.getMeasuredWidth(),
                        top + mlp.topMargin + view.getMeasuredHeight());
                left += view.getMeasuredWidth() + mlp.leftMargin + mlp.rightMargin;     //左坐标更新
            }
            left = 0;   //新一行,左坐标重置
            top += lineHeight;
        }
    }
}

在布局中调用的代码:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        inputEt = findViewById(R.id.search_input_edit);
        submitBtn = findViewById(R.id.search_submit_btn);
        showBtn = findViewById(R.id.search_show_btn);
        cleanBtn = findViewById(R.id.search_clean_btn);
        historyViewGroup = findViewById(R.id.search_history_view_group);

        submitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!TextUtils.isEmpty(inputEt.getText().toString())) {
                    MyApplication.getInstance().getDaoSession().insertOrReplace(new InputHistory(inputEt.getText().toString(),System.currentTimeMillis()));
                }
            }
        });
        showBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                List<InputHistory> historyList = MyApplication.getInstance().getDaoSession().getInputHistoryDao().queryBuilder()
                        .orderDesc(InputHistoryDao.Properties.InputTime).build().list();
                if (historyList != null && historyList.size() > 0) {
                    showInViewGroup(historyList);
                }
            }
        });
        cleanBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyApplication.getInstance().getDaoSession().deleteAll(InputHistory.class);
                if (historyViewGroup != null) {
                    historyViewGroup.removeAllViews();
                }
            }
        });
    }

    private void showInViewGroup(final List<InputHistory> historyList) {
        if (historyViewGroup != null) {
            Context mContext = historyViewGroup.getContext();
            historyViewGroup.removeAllViews();
            int size = historyList.size();
            for (int i = 0; i < size; i++) {
                final SearchHistoryCanDelTextView canDelTextView = new SearchHistoryCanDelTextView(mContext,i);
                canDelTextView.getDelTv().setText(historyList.get(i).getInputKey());
                ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                mlp.leftMargin = CrshUtils.dp2px(8,mContext);
                mlp.topMargin = CrshUtils.dp2px(11,mContext);
                mlp.rightMargin = CrshUtils.dp2px(4,mContext);
                canDelTextView.setLayoutParams(mlp);
                canDelTextView.setClickable(true);
                //点击和长按事件
                canDelTextView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //此处必须为INVISIBLE不能GONE,因为如果长按,可能导致宽度变化,布局改变,引入Bug
                        if (canDelTextView.getDelIv().getVisibility() == View.INVISIBLE) {
                            //做正常的业务处理
                        } else {
                            //做删除数据操作,更新视图
                            MyApplication.getInstance().getDaoSession().delete(historyList.get(canDelTextView.getSearchHistoryID()));
                            v.setVisibility(View.GONE);
                            historyViewGroup.removeView(v);
                        }
                    }
                });
                canDelTextView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        canDelTextView.getDelIv().setVisibility(View.VISIBLE);
                        return true;
                    }
                });
                historyViewGroup.addView(canDelTextView);
            }
        }
    }

另一部分的代码就是单个实体的布局,正常状态和待删除状态(右上角带删除图标)。逻辑很简单,代码不直接贴了。

后面还会更新这一文章的,需要整理的东西很多。

猜你喜欢

转载自blog.csdn.net/bendan50/article/details/85170234