如何在滚动视图Kivy中将小部件添加到标签

时间:2020-01-08 11:39:31

标签: python-3.x kivy kivy-language

我正在尝试创建一个看起来像电话上的标准消息传递应用程序(Whatsapp,Imessage等)的聊天页面。为此,我创建了一个包含ScrollView的屏幕,并向其中添加了标签小部件。

要获得Whatsapp / Imessage外观的预期效果,我想我希望每次按下发送按钮时将标签小部件添加到ScrollView的标签上,然后将文本输入框的文本添加到屏幕。

是否可以将标签添加到另一个标签?到目前为止,我一直在研究的每个问题都是关于将小部件添加到BoxLayout或GridLayout。

我也不知道每次按下按钮时一次如何改变标签的位置!

kv文件:

import UIKit
import PlaygroundSupport

// CLOUD VIEW WRAPPER - THIS IS THE CONTAINER FOR THE TAGS AND SETS UP THEIR FRAME
class CloudTagView: UIView {

  weak var delegate: TagViewDelegate?

  override var intrinsicContentSize: CGSize {
    return frame.size
  }

  var removeOnDismiss = true
  var resizeToFit = true

  var tags = [TagView]() {
    didSet {
      layoutSubviews()
    }
  }
  var padding = 5 {
    didSet {
      layoutSubviews()
    }
  }
  var maxLengthPerTag = 0 {
    didSet {
      layoutSubviews()
    }
  }

  public override init(frame: CGRect) {
    super.init(frame: frame)

    isUserInteractionEnabled = true
    clipsToBounds = true
  }

  public required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    isUserInteractionEnabled = true
    clipsToBounds = true
  }

  override func layoutSubviews() {
    for tag in subviews {
      tag.removeFromSuperview()
    }

    var xAxis = padding
    var yAxis = padding
    var maxHeight = 0

    for (index, tag) in tags.enumerated() {

      setMaxLengthIfNeededIn(tag)

      tag.delegate = self

      if index == 0 {
        maxHeight = Int(tag.frame.height)
      }else{
        let expectedWidth = xAxis + Int(tag.frame.width) + padding

        if expectedWidth > Int(frame.width) {
          yAxis += maxHeight + padding
          xAxis = padding
          maxHeight = Int(tag.frame.height)
        }

        if Int(tag.frame.height) > maxHeight {
          maxHeight = Int(tag.frame.height)
        }
      }
      tag.frame = CGRect(x: xAxis, y: yAxis, width: Int(tag.frame.size.width), height: Int(tag.frame.size.height))
      addSubview(tag)
      tag.layoutIfNeeded()
      xAxis += Int(tag.frame.width) + padding
    }

    if resizeToFit {
      frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: CGFloat(yAxis + maxHeight + padding))
    }
  }

  // MARK: Methods
  fileprivate func setMaxLengthIfNeededIn(_ tag: TagView) {
    if maxLengthPerTag > 0 && tag.maxLength != maxLengthPerTag {
      tag.maxLength = maxLengthPerTag
    }
  }

}


// EVERYTHING BELOW HERE IS JUST SETUP / REQUIRED TO RUN IN PLAYGROUND

class ViewController:UIViewController{

  let cloudView: CloudTagView = {
    let view = CloudTagView(frame: .zero)
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
  }()

  override func viewDidLoad() {
    super.viewDidLoad()


    let tags = ["these", "are", "my", "tags"]

    tags.forEach { tag in
      let t = TagView(text: tag)
      t.backgroundColor = .darkGray
      t.tintColor = .white
      cloudView.tags.append(t)
    }

    view.backgroundColor = .white

    view.addSubview(cloudView)
    NSLayoutConstraint.activate([
      cloudView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
      cloudView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
      cloudView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
      cloudView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
    ])


  }

}

// Tag View

class TagView: UIView {

  weak var delegate: TagViewDelegate?

  var text = "" {
    didSet {
      layoutSubviews()
    }
  }
  var marginTop = 5 {
    didSet {
      layoutSubviews()
    }
  }
  var marginLeft = 10 {
    didSet {
      layoutSubviews()
    }
  }
  var iconImage = UIImage(named: "close_tag_2", in: Bundle(for: CloudTagView.self), compatibleWith: nil) {
    didSet {
      layoutSubviews()
    }
  }
  var maxLength = 0 {
    didSet {
      layoutSubviews()
    }
  }

  override var backgroundColor: UIColor? {
    didSet {
      layoutSubviews()
    }
  }

  override var tintColor: UIColor? {
    didSet {
      layoutSubviews()
    }
  }

  var font: UIFont = UIFont.systemFont(ofSize: 12) {
    didSet {
      layoutSubviews()
    }
  }

  fileprivate let dismissView: UIView
  fileprivate let icon: UIImageView
  fileprivate let textLabel: UILabel

  public override init(frame: CGRect) {
    dismissView = UIView()
    icon = UIImageView()
    textLabel = UILabel()

    super.init(frame: frame)

    isUserInteractionEnabled = true

    addSubview(textLabel)
    addSubview(icon)
    addSubview(dismissView)

    dismissView.isUserInteractionEnabled = true
    textLabel.isUserInteractionEnabled = true

    dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.iconTapped)))
    textLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.labelTapped)))

    backgroundColor = UIColor(white: 0.0, alpha: 0.6)
    tintColor = UIColor.white
  }

  public required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  public init(text: String) {
    dismissView = UIView()
    icon = UIImageView()
    textLabel = UILabel()

    super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))

    isUserInteractionEnabled = true

    addSubview(textLabel)
    addSubview(icon)
    addSubview(dismissView)

    dismissView.isUserInteractionEnabled = true
    textLabel.isUserInteractionEnabled = true

    dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.iconTapped)))
    textLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.labelTapped)))

    self.text = text

    backgroundColor = UIColor(white: 0.0, alpha: 0.6)
    tintColor = UIColor.white
  }

  override func layoutSubviews() {
    icon.frame = CGRect(x: marginLeft, y: marginTop + 4, width: 8, height: 8)
    icon.image = iconImage?.withRenderingMode(.alwaysTemplate)
    icon.tintColor = tintColor

    let textLeft: Int

    if icon.image != nil {
      dismissView.isUserInteractionEnabled = true
      textLeft = marginLeft + Int(icon.frame.width ) + marginLeft / 2
    } else {
      dismissView.isUserInteractionEnabled = false
      textLeft = marginLeft
    }

    textLabel.frame = CGRect(x: textLeft, y: marginTop, width: 100, height: 20)
    textLabel.backgroundColor = UIColor(white: 0, alpha: 0.0)
    if maxLength > 0 && text.count > maxLength {
      textLabel.text = text.prefix(maxLength)+"..."
    }else{
      textLabel.text = text
    }
    textLabel.textAlignment = .center
    textLabel.font = font
    textLabel.textColor = tintColor
    textLabel.sizeToFit()

    let tagHeight = Int(max(textLabel.frame.height,14)) + marginTop * 2
    let tagWidth = textLeft + Int(max(textLabel.frame.width,14)) + marginLeft

    let dismissLeft = Int(icon.frame.origin.x) + Int(icon.frame.width) + marginLeft / 2
    dismissView.frame = CGRect(x: 0, y: 0, width: dismissLeft, height: tagHeight)

    frame = CGRect(x: Int(frame.origin.x), y: Int(frame.origin.y), width: tagWidth, height: tagHeight)
    layer.cornerRadius = bounds.height / 2
  }

  // MARK: Actions

  @objc func iconTapped(){
    delegate?.tagDismissed?(self)
  }

  @objc func labelTapped(){
    delegate?.tagTouched?(self)
  }

}

// MARK: TagViewDelegate
@objc protocol TagViewDelegate {

  @objc optional func tagTouched(_ tag: TagView)

  @objc optional func tagDismissed(_ tag: TagView)

}

extension CloudTagView: TagViewDelegate {

  public func tagDismissed(_ tag: TagView) {
    delegate?.tagDismissed?(tag)

    if removeOnDismiss {
      if let index = tags.firstIndex(of: tag) {
        tags.remove(at: index)
      }
    }
  }

  public func tagTouched(_ tag: TagView) {
    delegate?.tagTouched?(tag)
  }

}


let viewController = ViewController()
PlaygroundPage.current.liveView = viewController
PlaygroundPage.current.needsIndefiniteExecution = true

py文件:

WindowManager:
    ChatPage:

<ChatPage>:
    name: "chat_page"
    layout_content: layout_content

    NavigationLayout:
        id: nav_layout
        MDNavigationDrawer:
            NavigationDrawerIconButton:
                text: "Test"

        FloatLayout:
            MDToolbar:
                pos_hint: {'top': 1}
                md_bg_color: 0.2, 0.6, 1, 1

            ScrollView:
                size_hint: 1, 0.6
                pos_hint: {"top" : 0.8, "bottom" : 0.5}
                GridLayout:
                    id: layout_content
                    cols: 1
                    size_hint_y: None
                    height: self.minimum_height
                    canvas:
                        Color:
                            rgba: (1, 1, 1, 1)
                        Rectangle:
                            size: self.size
                            pos: self.pos

                    Label:
                        text_size: self.width - 20, None
                        size_hint_y: None
                        height: self.texture_size[1]
                        color: 0,0,0,1

            BoxLayout:
                TextInput:
                    id: msg
                    hint_text: "Type your message here"
                    pos_hint: {"x": 0, "top": 0.15}
                    size_hint: 0.75, 0.15
                Button:
                    text: "Send"
                    background_normal: ""
                    background_color: 0, 0.6, 0, 1
                    pos_hint: {"x": 0.75, "top": 0.15}
                    size_hint: 0.25, 0.15
                    on_release: root.btn_press()

<SmoothLabel@Label>:
    background_color: 0,0,0,0
    background_normal: ""
    back_color: 1,0,1,1
    border_radius: [6]
    canvas.before:
        Color:
            rgba: 0.2,0.6,1,1 #This changes the label colour
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: self.border_radius

谢谢

1 个答案:

答案 0 :(得分:0)

对代码进行了一些更改(除了删除所有kivyMD之外)以使其正常工作:

首先,我将您的SmoothLabel中的kv规则更改为:

<SmoothLabel>:
    size_hint: None, None
    size: self.texture_size
    background_color: 0,0,0,0
    background_normal: ""
    back_color: 1,0,1,1
    border_radius: [6]
    canvas.before:
        Color:
            rgba: 0.2,0.6,1,1 #This changes the label colour
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: self.border_radius

取出@Label,因为您的python代码中已经指定了size_hint,并添加了sizeGridLayout(否则,SmoothLabel决定{ {1}}个孩子)。

此外,删除了

background_color=(0.2, 0.6, 1, 1)

来自SmoothLabel的创作(这对我来说是个例外),因此ChatPage的定义现在看起来像:

class ChatPage(Screen):
    layout_content = ObjectProperty(None)
    def btn_press(self):
        if self.ids.msg.text:
            self.layout_content.add_widget(SmoothLabel(text=self.ids.msg.text))
            self.ids.msg.text = ""
        else:
            pass

Label小部件没有background_color属性。并且,由于我在规则中添加了size_hint,因此将其从SmoothLabel()调用中删除了。另外,GridLayout不支持pos_hint

这足以使代码对我有用。