├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── blank │ │ └── simple │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── blank-stu.json │ │ ├── blank.json │ │ ├── blank1.json │ │ └── blank2.json │ ├── java │ │ └── com │ │ │ └── blank │ │ │ └── simple │ │ │ ├── BlankActivity.kt │ │ │ ├── GsonUtils.kt │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_blank.xml │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── blank │ └── simple │ └── ExampleUnitTest.java ├── blank ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xc │ │ └── blank │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xc │ │ │ └── blank │ │ │ ├── BlankRootView.kt │ │ │ ├── BlankView.java │ │ │ ├── ChoiceBlankBean.kt │ │ │ ├── FillAnswerInfo.java │ │ │ ├── FlowLayout.java │ │ │ ├── LineInfo.java │ │ │ ├── Status.java │ │ │ └── TextInfo.java │ └── res │ │ ├── drawable │ │ ├── btn_rectangle_gray_b3b3b3.xml │ │ ├── btn_rectangle_gray_e0e0e0.xml │ │ ├── btn_rectangle_gray_ff7171.xml │ │ ├── selector_task_answer.xml │ │ └── selector_task_answer_text.xml │ │ ├── layout │ │ ├── blank_root_view.xml │ │ └── item_task_answer.xml │ │ └── values │ │ ├── attrs.xml │ │ └── dimens.xml │ └── test │ └── java │ └── com │ └── xc │ └── blank │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── simple.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 一款不错的选词填空题的自定义view,样式可以自己定制。 2 | ![simple](https://github.com/xiangcman/BlankView/blob/master/images/simple.gif) 3 | 4 | ### 使用: 5 | - 如果想直接有流失布局的选项样式直接用: 6 | ``` 7 | 11 | ``` 12 | 如果想自己定义选项的view那你可以直接定义BlankView: 13 | ``` 14 | 21 | ``` 22 | 后期会考虑加入自定义属性!!! 23 | 24 | 如果想第一时间看demo效果扫描下面二维码: 25 | 26 | ![image](https://upload-images.jianshu.io/upload_images/2528336-6141c1e15ee83177?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 27 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.3" 8 | 9 | defaultConfig { 10 | applicationId "com.blank.simple" 11 | minSdkVersion 16 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: "libs", include: ["*.jar"]) 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | implementation project(path: ':blank') 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 35 | implementation 'com.google.code.gson:gson:2.8.6' 36 | implementation "androidx.core:core-ktx:+" 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 38 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0' 39 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/blank/simple/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.blank.simple; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.blank.simple", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/assets/blank-stu.json: -------------------------------------------------------------------------------- 1 | { 2 | "questionContent": "(1)大会选举萧同兹一人为_____\n(2)大会选举赵敏恒(《中央日报》)、周钦岳(《新蜀报》)等十九人为_____\n(3)大会选举杜协民(《国民公报》)、潘梓年(《新华日报》)等七人为_____", 3 | "stuAnswer": [ 4 | { 5 | "optValue": "①理事", 6 | "answer": 1 7 | }, 8 | { 9 | "optValue": "②候补理事", 10 | "answer": 0 11 | }, 12 | { 13 | "optValue": "③理事长", 14 | "answer": 0 15 | } 16 | ], 17 | 18 | "choiceOptions": [ 19 | { 20 | "optValue": "①理事" 21 | }, 22 | { 23 | "optValue": "②候补理事" 24 | }, 25 | { 26 | "optValue": "③理事长" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /app/src/main/assets/blank.json: -------------------------------------------------------------------------------- 1 | { 2 | "questionContent": "①中医通过望、闻、问、切等方法来了解_____,作出诊断\n②孩子过多玩网络游戏,父母应适当加以_____。\n③他们心中依然珍藏着那段美好的回忆,_____他们已经远离了那段激情燃烧的岁月。", 3 | "choiceOptions": [ 4 | { 5 | "optValue": "病症" 6 | }, 7 | { 8 | "optValue": "病征" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /app/src/main/assets/blank1.json: -------------------------------------------------------------------------------- 1 | { 2 | "questionContent": "(1)床前明月_____\n(2)疑是地上_____\n(3)举头望_____\n(4)低头_____", 3 | "choiceOptions": [ 4 | { 5 | "optValue": "①光" 6 | }, 7 | { 8 | "optValue": "②明月" 9 | }, 10 | { 11 | "optValue": "③霜" 12 | }, 13 | { 14 | "optValue": "④思故乡" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /app/src/main/assets/blank2.json: -------------------------------------------------------------------------------- 1 | { 2 | "questionContent": "《祖国啊,我亲爱的祖国》这首诗的艺术力量不仅在于诗人用①_____来关照内心的情感记忆,出格而入理地描绘了祖国深重的②_____,以及新生的希望,光明的前程;而且还在于诗人把③_____摆进④_____相交错的现实中,寓己于形象,对祖国的过去和将来进行了深刻的思考,表达了深挚的热爱和⑤_____的决心", 3 | "choiceOptions": [ 4 | { 5 | "optValue": "幼年/小时候/自幼/童年" 6 | }, 7 | { 8 | "optValue": "观察/自然/思考/热爱(1分)" 9 | }, 10 | { 11 | "optValue": "吸引/好奇" 12 | }, 13 | { 14 | "optValue": "想象力/求知欲/创造力/好奇心/动脑/学习/激发" 15 | }, 16 | { 17 | "optValue": "起点/萌芽/今后/科学/昆虫学家/文学家/贡献/喜爱/赞赏" 18 | }, 19 | { 20 | "optValue": "所以/因此/故" 21 | }, 22 | { 23 | "optValue": "查询/检索/搜索" 24 | }, 25 | { 26 | "optValue": "认真/揭穿" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blank/simple/BlankActivity.kt: -------------------------------------------------------------------------------- 1 | package com.blank.simple 2 | 3 | import android.os.Bundle 4 | import android.text.TextUtils 5 | import android.util.Log 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.xc.blank.ChoiceBlankBean 8 | import kotlinx.android.synthetic.main.activity_blank.blank_root_view 9 | import kotlinx.android.synthetic.main.activity_blank.get_answer 10 | import kotlinx.android.synthetic.main.activity_blank.tv 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.GlobalScope 13 | import kotlinx.coroutines.Job 14 | import kotlinx.coroutines.coroutineScope 15 | import kotlinx.coroutines.launch 16 | import kotlinx.coroutines.withContext 17 | import java.lang.StringBuilder 18 | 19 | class BlankActivity : AppCompatActivity() { 20 | lateinit var launch: Job 21 | lateinit var path: String 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_blank) 25 | path = intent.getStringExtra("path") 26 | initData() 27 | get_answer.setOnClickListener { 28 | val answerResult = blank_root_view.getAnswerResult() 29 | val sb = StringBuilder() 30 | answerResult.forEach { 31 | if (!TextUtils.isEmpty(it.optValue)) { 32 | sb.append("${it.optValue};") 33 | } else { 34 | sb.append(";") 35 | } 36 | } 37 | tv.text = sb.toString() 38 | } 39 | } 40 | 41 | private fun initData() { 42 | //最原始的协程,阻塞式的,需要在onDestroy的时候关掉协程 43 | launch = GlobalScope.launch(Dispatchers.IO) { 44 | val jsonData = getJsonData() 45 | withContext(Dispatchers.Main) { 46 | blank_root_view.setText(jsonData) 47 | } 48 | } 49 | 50 | } 51 | 52 | override fun onDestroy() { 53 | super.onDestroy() 54 | launch.cancel() 55 | } 56 | 57 | private suspend fun getJsonData() = coroutineScope { 58 | var bean: ChoiceBlankBean? = null 59 | try { 60 | assets.open(path).use { inputStream -> 61 | bean = inputStream.reader().use { reader -> 62 | val gsonToBean = GsonToBean(reader, ChoiceBlankBean::class.java) 63 | gsonToBean 64 | } 65 | bean 66 | } 67 | bean 68 | } catch (e: Exception) { 69 | bean 70 | 71 | } 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blank/simple/GsonUtils.kt: -------------------------------------------------------------------------------- 1 | package com.blank.simple 2 | 3 | import com.google.gson.Gson 4 | import java.io.InputStreamReader 5 | 6 | fun GsonToBean(reader: InputStreamReader, clas: Class): T { 7 | val gson = Gson() 8 | return gson.fromJson(reader, clas) 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/blank/simple/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.blank.simple; 2 | 3 | import android.content.Intent; 4 | import android.view.View; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | } 15 | 16 | public void toBlank(View view) { 17 | Intent intent = new Intent(this, BlankActivity.class); 18 | switch (view.getId()) { 19 | case R.id.goToDragBtn: 20 | intent.putExtra("path", "blank.json"); 21 | break; 22 | case R.id.goToDrag1Btn: 23 | intent.putExtra("path", "blank1.json"); 24 | break; 25 | case R.id.goToDrag2Btn: 26 | intent.putExtra("path", "blank-stu.json"); 27 | break; 28 | case R.id.goToDrag3Btn: 29 | intent.putExtra("path", "blank2.json"); 30 | break; 31 | } 32 | startActivity(intent); 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_blank.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 |